From ff0b9e30de94a5ab7fe33e25a806654f59494988 Mon Sep 17 00:00:00 2001 From: Kuwa Lee Date: Wed, 12 Jun 2024 08:48:35 +0800 Subject: [PATCH 001/127] TW: Update and clean --- src/locale/locales/zh-TW/messages.po | 176 ++++++++------------------- 1 file changed, 54 insertions(+), 122 deletions(-) diff --git a/src/locale/locales/zh-TW/messages.po b/src/locale/locales/zh-TW/messages.po index 09f48c1d0d..5cb9ccfbb4 100644 --- a/src/locale/locales/zh-TW/messages.po +++ b/src/locale/locales/zh-TW/messages.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: zh-TW for bluesky-social-app\n" "POT-Creation-Date: \n" "Report-Msgid-Bugs-To: Kuwa Lee , Frudrax Cheng \n" -"PO-Revision-Date: 2024-06-11 16:18+0800\n" +"PO-Revision-Date: 2024-06-12 08:47+0800\n" "Last-Translator: \n" "Language-Team: Frudrax Cheng , Kuwa Lee , noeFly, snowleo208, Kisaragi Hiu, Yi-Jyun Pan, toto6038, cirx1e\n" "Language: zh_TW\n" @@ -33,14 +33,13 @@ msgstr "{0, plural, one {該帳號有 # 個標記} other {該帳號有 # 個標 msgid "{0, plural, one {# label has been placed on this content} other {# labels have been placed on this content}}" msgstr "{0, plural, one {該內容有 # 個標記} other {該內容有 # 個標記}}" -#: src/view/com/util/post-ctrls/RepostButton.tsx:65 #: src/view/com/util/post-ctrls/RepostButton.tsx:65 msgid "{0, plural, one {# repost} other {# reposts}}" msgstr "{0, plural, one {# 個轉貼} other {# 個轉貼}}" #: src/components/KnownFollowers.tsx:179 msgid "{0, plural, one {and # other} other {and # others}}" -msgstr "" +msgstr "{0, plural, one {和其他 # 人} other {和其他 # 人}}" #: src/components/ProfileHoverCard/index.web.tsx:376 #: src/screens/Profile/Header/Metrics.tsx:23 @@ -60,7 +59,6 @@ msgstr "{0, plural, one {喜歡(# 個喜歡)} other {喜歡(# 個喜歡) msgid "{0, plural, one {like} other {likes}}" msgstr "{0, plural, one {喜歡} other {喜歡}}" -#: src/view/com/feeds/FeedSourceCard.tsx:301 #: src/view/com/feeds/FeedSourceCard.tsx:301 msgid "{0, plural, one {Liked by # user} other {Liked by # users}}" msgstr "{0, plural,one {# 個用戶已喜歡} other {# 個用戶已喜歡}}" @@ -277,7 +275,6 @@ msgstr "將以下 DNS 記錄新增到您的網域:" msgid "Add to Lists" msgstr "新增至列表" -#: src/view/com/feeds/FeedSourceCard.tsx:267 #: src/view/com/feeds/FeedSourceCard.tsx:267 msgid "Add to my feeds" msgstr "加入到我的動態源" @@ -287,7 +284,6 @@ msgstr "加入到我的動態源" msgid "Added to list" msgstr "新增至列表" -#: src/view/com/feeds/FeedSourceCard.tsx:126 #: src/view/com/feeds/FeedSourceCard.tsx:126 msgid "Added to my feeds" msgstr "加入到我的動態源" @@ -469,12 +465,11 @@ msgstr "您確定要刪除這則訊息嗎?該訊息將為您刪除,但不會 msgid "Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for the other participant." msgstr "您確定要離開此對話嗎?您的訊息將為您刪除,但不會為其他參與者刪除。" -#: src/view/com/feeds/FeedSourceCard.tsx:314 #: src/view/com/feeds/FeedSourceCard.tsx:314 msgid "Are you sure you want to remove {0} from your feeds?" msgstr "您確定要從您的動態中移除 {0} 嗎?" -#: src/view/com/composer/Composer.tsx:664 +#: src/view/com/composer/Composer.tsx:630 msgid "Are you sure you'd like to discard this draft?" msgstr "您確定要捨棄此草稿嗎?" @@ -582,7 +577,7 @@ msgstr "被封鎖的帳號無法在您的討論串中回覆、提及您,或以 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours." msgstr "被封鎖的帳號無法在您的討論串中回覆、提及您,或以其他方式與您互動。您將看不到他們的內容,他們也會被阻止看到您的內容。" -#: src/view/com/post-thread/PostThread.tsx:362 +#: src/view/com/post-thread/PostThread.tsx:363 msgid "Blocked post." msgstr "已封鎖貼文。" @@ -669,8 +664,8 @@ msgstr "只能包含字母、數字、空格、破折號及底線。長度必須 #: src/components/Prompt.tsx:121 #: src/components/TagMenu/index.tsx:268 #: src/screens/Deactivated.tsx:161 -#: src/view/com/composer/Composer.tsx:460 -#: src/view/com/composer/Composer.tsx:466 +#: src/view/com/composer/Composer.tsx:432 +#: src/view/com/composer/Composer.tsx:438 #: src/view/com/modals/ChangeEmail.tsx:213 #: src/view/com/modals/ChangeEmail.tsx:215 #: src/view/com/modals/ChangeHandle.tsx:148 @@ -687,7 +682,7 @@ msgstr "只能包含字母、數字、空格、破折號及底線。長度必須 #: src/view/com/modals/VerifyEmail.tsx:255 #: src/view/com/modals/VerifyEmail.tsx:261 #: src/view/com/util/post-ctrls/RepostButton.tsx:138 -#: src/view/screens/Search/Search.tsx:738 +#: src/view/screens/Search/Search.tsx:735 #: src/view/shell/desktop/Search.tsx:218 msgid "Cancel" msgstr "取消" @@ -716,7 +711,6 @@ msgstr "取消圖片裁剪" msgid "Cancel profile editing" msgstr "取消編輯個人檔案" -#: src/view/com/util/post-ctrls/RepostButton.tsx:132 #: src/view/com/util/post-ctrls/RepostButton.tsx:132 msgid "Cancel quote post" msgstr "取消引用貼文" @@ -950,7 +944,7 @@ msgstr "關閉底部導覽列" msgid "Closes password update alert" msgstr "關閉密碼更新警告" -#: src/view/com/composer/Composer.tsx:462 +#: src/view/com/composer/Composer.tsx:434 msgid "Closes post composer and discards post draft" msgstr "關閉貼文編輯頁並捨棄草稿" @@ -987,7 +981,7 @@ msgstr "完成初始設定並開始使用您的帳號" msgid "Complete the challenge" msgstr "完成驗證" -#: src/view/com/composer/Composer.tsx:583 +#: src/view/com/composer/Composer.tsx:551 msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" msgstr "撰寫貼文的長度最多為 {MAX_GRAPHEME_LENGTH} 個字元" @@ -1089,7 +1083,6 @@ msgstr "彈出式選單背景,點擊以關閉選單。" #: src/screens/Onboarding/StepInterests/index.tsx:253 #: src/screens/Onboarding/StepProfile/index.tsx:269 -#: src/screens/Onboarding/StepProfile/index.tsx:269 msgid "Continue" msgstr "繼續" @@ -1099,11 +1092,10 @@ msgstr "以 {0} 繼續 (目前已登入)" #: src/view/com/post-thread/PostThreadLoadMore.tsx:52 msgid "Continue thread..." -msgstr "" +msgstr "繼續載入討論串…" #: src/screens/Onboarding/StepInterests/index.tsx:250 #: src/screens/Onboarding/StepProfile/index.tsx:266 -#: src/screens/Onboarding/StepProfile/index.tsx:266 #: src/screens/Signup/index.tsx:213 msgid "Continue to next step" msgstr "繼續下一步" @@ -1212,7 +1204,6 @@ msgstr "建立帳號" msgid "Create an account" msgstr "建立一個帳號" -#: src/screens/Onboarding/StepProfile/index.tsx:283 #: src/screens/Onboarding/StepProfile/index.tsx:283 msgid "Create an avatar instead" msgstr "或是建立一個頭像" @@ -1358,7 +1349,7 @@ msgstr "刪除這條貼文?" msgid "Deleted" msgstr "已刪除" -#: src/view/com/post-thread/PostThread.tsx:348 +#: src/view/com/post-thread/PostThread.tsx:349 msgid "Deleted post." msgstr "已刪除貼文。" @@ -1377,7 +1368,7 @@ msgstr "描述" msgid "Descriptive alt text" msgstr "生動的替代文字" -#: src/view/com/composer/Composer.tsx:270 +#: src/view/com/composer/Composer.tsx:277 msgid "Did you want to say anything?" msgstr "有什麼想說的嗎?" @@ -1410,11 +1401,11 @@ msgstr "關閉觸覺回饋" msgid "Disabled" msgstr "停用" -#: src/view/com/composer/Composer.tsx:666 +#: src/view/com/composer/Composer.tsx:632 msgid "Discard" msgstr "捨棄" -#: src/view/com/composer/Composer.tsx:663 +#: src/view/com/composer/Composer.tsx:629 msgid "Discard draft?" msgstr "捨棄草稿?" @@ -1466,8 +1457,6 @@ msgstr "網域已驗證!" #: src/components/forms/DateField/index.tsx:80 #: src/screens/Onboarding/StepProfile/index.tsx:322 #: src/screens/Onboarding/StepProfile/index.tsx:325 -#: src/screens/Onboarding/StepProfile/index.tsx:322 -#: src/screens/Onboarding/StepProfile/index.tsx:325 #: src/view/com/auth/server-input/index.tsx:169 #: src/view/com/auth/server-input/index.tsx:170 #: src/view/com/modals/AddAppPasswords.tsx:242 @@ -1545,16 +1534,16 @@ msgstr "例如:多次張貼廣告的用戶。" msgid "Each code works once. You'll receive more invite codes periodically." msgstr "每個邀請碼僅能使用一次。您將定期收到更多的邀請碼。" -#: src/view/screens/Feeds.tsx:400 -#: src/view/screens/Feeds.tsx:471 -msgid "Edit" -msgstr "" - #: src/view/com/lists/ListMembers.tsx:149 msgctxt "action" msgid "Edit" msgstr "編輯" +#: src/view/screens/Feeds.tsx:400 +#: src/view/screens/Feeds.tsx:471 +msgid "Edit" +msgstr "編輯" + #: src/view/com/util/UserAvatar.tsx:312 #: src/view/com/util/UserBanner.tsx:92 msgid "Edit avatar" @@ -1594,11 +1583,6 @@ msgstr "編輯個人檔案" msgid "Edit Profile" msgstr "編輯個人檔案" -#: src/view/com/home/HomeHeaderLayout.web.tsx:76 -#: src/view/screens/Feeds.tsx:416 -#~ msgid "Edit Saved Feeds" -#~ msgstr "編輯已儲存之動態源" - #: src/view/com/modals/CreateOrEditList.tsx:234 msgid "Edit User List" msgstr "編輯用戶列表" @@ -1899,7 +1883,6 @@ msgstr "無法更新設定" msgid "Feed" msgstr "動態" -#: src/view/com/feeds/FeedSourceCard.tsx:251 #: src/view/com/feeds/FeedSourceCard.tsx:251 msgid "Feed by {0}" msgstr "{0} 建立的動態源" @@ -2010,7 +1993,7 @@ msgstr "回追蹤" #: src/components/KnownFollowers.tsx:169 msgid "Followed by" -msgstr "" +msgstr "也被以下用戶跟隨:" #: src/view/com/profile/ProfileCard.tsx:227 msgid "Followed by {0}" @@ -2035,12 +2018,12 @@ msgstr "跟隨者" #: src/Navigation.tsx:177 msgid "Followers of @{0} that you know" -msgstr "" +msgstr "您所認識的 @{0} 之跟隨者" #: src/screens/Profile/KnownFollowers.tsx:108 #: src/screens/Profile/KnownFollowers.tsx:118 msgid "Followers you know" -msgstr "" +msgstr "您也認識的跟隨者" #: src/components/ProfileHoverCard/index.web.tsx:411 #: src/components/ProfileHoverCard/index.web.tsx:422 @@ -2130,7 +2113,6 @@ msgstr "開始" msgid "Get Started" msgstr "開始" -#: src/screens/Onboarding/StepProfile/index.tsx:225 #: src/screens/Onboarding/StepProfile/index.tsx:225 msgid "Give your profile a face" msgstr "為您的個人檔案增添新顏" @@ -2176,7 +2158,7 @@ msgstr "前往首頁" msgid "Go Home" msgstr "前往首頁" -#: src/screens/Messages/List/ChatListItem.tsx:209 +#: src/screens/Messages/List/ChatListItem.tsx:211 msgid "Go to conversation with {0}" msgstr "與 {0} 對話" @@ -2226,7 +2208,6 @@ msgstr "遇到問題?" msgid "Help" msgstr "幫助" -#: src/screens/Onboarding/StepProfile/index.tsx:228 #: src/screens/Onboarding/StepProfile/index.tsx:228 msgid "Help people know you're not a bot by uploading a picture or creating an avatar." msgstr "透過上傳圖片或建立頭像來幫助人們知道您不是機器人。" @@ -2269,27 +2250,22 @@ msgstr "隱藏這則貼文?" msgid "Hide user list" msgstr "隱藏用戶列表" -#: src/view/com/posts/FeedErrorMessage.tsx:117 #: src/view/com/posts/FeedErrorMessage.tsx:117 msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." msgstr "抱歉,與動態源的伺服器連線時發生了某種問題。請向該動態源的擁有者報告這個問題。" -#: src/view/com/posts/FeedErrorMessage.tsx:105 #: src/view/com/posts/FeedErrorMessage.tsx:105 msgid "Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue." msgstr "抱歉,動態源的伺服器似乎設定錯誤。請向該動態源的擁有者報告這個問題。" -#: src/view/com/posts/FeedErrorMessage.tsx:111 #: src/view/com/posts/FeedErrorMessage.tsx:111 msgid "Hmm, the feed server appears to be offline. Please let the feed owner know about this issue." msgstr "抱歉,動態源的伺服器似乎已離線。請向該動態源的擁有者報告這個問題。" -#: src/view/com/posts/FeedErrorMessage.tsx:108 #: src/view/com/posts/FeedErrorMessage.tsx:108 msgid "Hmm, the feed server gave a bad response. Please let the feed owner know about this issue." msgstr "抱歉,動態源的伺服器給出了錯誤的回應。請向該動態源的擁有者報告這個問題。" -#: src/view/com/posts/FeedErrorMessage.tsx:102 #: src/view/com/posts/FeedErrorMessage.tsx:102 msgid "Hmm, we're having trouble finding this feed. It may have been deleted." msgstr "抱歉,我們無法找到這個動態源,它可能已被刪除。" @@ -2640,7 +2616,6 @@ msgstr "列表頭像" msgid "List blocked" msgstr "列表已封鎖" -#: src/view/com/feeds/FeedSourceCard.tsx:253 #: src/view/com/feeds/FeedSourceCard.tsx:253 msgid "List by {0}" msgstr "列表由 {0} 建立" @@ -2678,7 +2653,6 @@ msgstr "列表" msgid "Lists blocking this user:" msgstr "封鎖此用戶的列表:" -#: src/view/screens/Notifications.tsx:184 #: src/view/screens/Notifications.tsx:184 msgid "Load new notifications" msgstr "載入新的通知" @@ -2778,7 +2752,6 @@ msgstr "給 {0} 傳送訊息" msgid "Message deleted" msgstr "訊息已刪除" -#: src/view/com/posts/FeedErrorMessage.tsx:200 #: src/view/com/posts/FeedErrorMessage.tsx:200 msgid "Message from server: {0}" msgstr "來自伺服器的訊息:{0}" @@ -3069,7 +3042,7 @@ msgctxt "action" msgid "New post" msgstr "新貼文" -#: src/view/screens/Feeds.tsx:627 +#: src/view/screens/Feeds.tsx:600 #: src/view/screens/Notifications.tsx:193 #: src/view/screens/Profile.tsx:464 #: src/view/screens/ProfileFeed.tsx:426 @@ -3151,7 +3124,6 @@ msgstr "還沒有訊息" msgid "No more conversations to show" msgstr "已經沒有對話啦!" -#: src/view/com/notifications/Feed.tsx:118 #: src/view/com/notifications/Feed.tsx:118 msgid "No notifications yet!" msgstr "還沒有通知!" @@ -3249,7 +3221,7 @@ msgstr "通知音效" msgid "Notification Sounds" msgstr "通知音效" -#: src/Navigation.tsx:516 +#: src/Navigation.tsx:499 #: src/view/screens/Notifications.tsx:132 #: src/view/screens/Notifications.tsx:169 #: src/view/shell/bottom-bar/BottomBar.tsx:227 @@ -3301,11 +3273,10 @@ msgstr "最舊的回覆優先" msgid "Onboarding reset" msgstr "重新開始引導流程" -#: src/view/com/composer/Composer.tsx:531 +#: src/view/com/composer/Composer.tsx:503 msgid "One or more images is missing alt text." msgstr "至少有一張圖片缺失了替代文字。" -#: src/screens/Onboarding/StepProfile/index.tsx:117 #: src/screens/Onboarding/StepProfile/index.tsx:117 msgid "Only .jpg and .png files are supported" msgstr "僅支援 .jpg 或 .png 格式的圖片" @@ -3336,18 +3307,17 @@ msgstr "開啟" msgid "Open {name} profile shortcut menu" msgstr "開啟 {name} 個人檔案快捷選單" -#: src/screens/Onboarding/StepProfile/index.tsx:277 #: src/screens/Onboarding/StepProfile/index.tsx:277 msgid "Open avatar creator" msgstr "開啟頭像建立工具" -#: src/screens/Messages/List/ChatListItem.tsx:217 -#: src/screens/Messages/List/ChatListItem.tsx:218 +#: src/screens/Messages/List/ChatListItem.tsx:219 +#: src/screens/Messages/List/ChatListItem.tsx:220 msgid "Open conversation options" msgstr "開啟對話選項" -#: src/view/com/composer/Composer.tsx:647 -#: src/view/com/composer/Composer.tsx:648 +#: src/view/com/composer/Composer.tsx:613 +#: src/view/com/composer/Composer.tsx:614 msgid "Open emoji picker" msgstr "開啟表情符號選擇器" @@ -3474,11 +3444,6 @@ msgstr "開啟內容管理設定" msgid "Opens password reset form" msgstr "開啟密碼重設表單" -#: src/view/com/home/HomeHeaderLayout.web.tsx:77 -#: src/view/screens/Feeds.tsx:417 -#~ msgid "Opens screen to edit Saved Feeds" -#~ msgstr "開啟編輯已儲存的動態源之畫面" - #: src/view/screens/Settings/index.tsx:617 msgid "Opens screen with all saved feeds" msgstr "開啟包含所有已儲存的動態源之畫面" @@ -3701,7 +3666,7 @@ msgstr "請以 @{0} 的身分登入" msgid "Please Verify Your Email" msgstr "請驗證您的電子郵件地址" -#: src/view/com/composer/Composer.tsx:274 +#: src/view/com/composer/Composer.tsx:281 msgid "Please wait for your link card to finish loading" msgstr "請等待您的連結預覽載入完畢" @@ -3713,13 +3678,13 @@ msgstr "政治" msgid "Porn" msgstr "色情內容" -#: src/view/com/composer/Composer.tsx:505 -#: src/view/com/composer/Composer.tsx:513 +#: src/view/com/composer/Composer.tsx:477 +#: src/view/com/composer/Composer.tsx:485 msgctxt "action" msgid "Post" msgstr "發佈" -#: src/view/com/post-thread/PostThread.tsx:426 +#: src/view/com/post-thread/PostThread.tsx:430 msgctxt "description" msgid "Post" msgstr "貼文" @@ -3738,7 +3703,7 @@ msgstr "@{0} 的貼文" msgid "Post deleted" msgstr "貼文已刪除" -#: src/view/com/post-thread/PostThread.tsx:192 +#: src/view/com/post-thread/PostThread.tsx:193 msgid "Post hidden" msgstr "貼文已隱藏" @@ -3760,8 +3725,8 @@ msgstr "貼文語言" msgid "Post Languages" msgstr "貼文語言" -#: src/view/com/post-thread/PostThread.tsx:187 -#: src/view/com/post-thread/PostThread.tsx:199 +#: src/view/com/post-thread/PostThread.tsx:188 +#: src/view/com/post-thread/PostThread.tsx:200 msgid "Post not found" msgstr "找不到貼文" @@ -3777,7 +3742,6 @@ msgstr "貼文" msgid "Posts can be muted based on their text, their tags, or both." msgstr "可以靜音貼文所包含的文字和標籤。" -#: src/view/com/posts/FeedErrorMessage.tsx:68 #: src/view/com/posts/FeedErrorMessage.tsx:68 msgid "Posts hidden" msgstr "貼文已隱藏" @@ -3803,7 +3767,7 @@ msgstr "按下以重試" #: src/components/KnownFollowers.tsx:111 msgid "Press to view followers of this account that you also follow" -msgstr "" +msgstr "按下以查看被您跟隨且跟隨此帳戶者" #: src/view/com/lightbox/Lightbox.web.tsx:150 msgid "Previous image" @@ -3871,16 +3835,14 @@ msgstr "公開且可共享的批量靜音或封鎖列表。" msgid "Public, shareable lists which can drive feeds." msgstr "公開且可共享的列表,可作為動態源使用。" -#: src/view/com/composer/Composer.tsx:490 +#: src/view/com/composer/Composer.tsx:462 msgid "Publish post" msgstr "發佈貼文" -#: src/view/com/composer/Composer.tsx:490 +#: src/view/com/composer/Composer.tsx:462 msgid "Publish reply" msgstr "發佈回覆" -#: src/view/com/util/post-ctrls/RepostButton.tsx:115 -#: src/view/com/util/post-ctrls/RepostButton.tsx:127 #: src/view/com/util/post-ctrls/RepostButton.tsx:115 #: src/view/com/util/post-ctrls/RepostButton.tsx:127 #: src/view/com/util/post-ctrls/RepostButton.web.tsx:78 @@ -3916,13 +3878,12 @@ msgstr "重新連線" msgid "Reload conversations" msgstr "重新載入對話" -#: src/components/dialogs/MutedWords.tsx:288 +#: src/components/dialogs/MutedWords.tsx:286 #: src/view/com/feeds/FeedSourceCard.tsx:317 #: src/view/com/modals/ListAddRemoveUsers.tsx:268 #: src/view/com/modals/SelfLabel.tsx:84 #: src/view/com/modals/UserAddRemoveLists.tsx:230 #: src/view/com/posts/FeedErrorMessage.tsx:212 -#: src/view/com/posts/FeedErrorMessage.tsx:212 msgid "Remove" msgstr "刪除" @@ -3942,20 +3903,16 @@ msgstr "刪除橫幅" msgid "Remove embed" msgstr "刪除嵌入" -#: src/view/com/posts/FeedErrorMessage.tsx:168 #: src/view/com/posts/FeedErrorMessage.tsx:168 #: src/view/com/posts/FeedShutdownMsg.tsx:113 #: src/view/com/posts/FeedShutdownMsg.tsx:117 msgid "Remove feed" msgstr "刪除動態源" -#: src/view/com/posts/FeedErrorMessage.tsx:209 #: src/view/com/posts/FeedErrorMessage.tsx:209 msgid "Remove feed?" msgstr "刪除動態源?" -#: src/view/com/feeds/FeedSourceCard.tsx:188 -#: src/view/com/feeds/FeedSourceCard.tsx:266 #: src/view/com/feeds/FeedSourceCard.tsx:188 #: src/view/com/feeds/FeedSourceCard.tsx:266 #: src/view/screens/ProfileFeed.tsx:330 @@ -3964,7 +3921,6 @@ msgstr "刪除動態源?" msgid "Remove from my feeds" msgstr "從我的動態源中刪除" -#: src/view/com/feeds/FeedSourceCard.tsx:312 #: src/view/com/feeds/FeedSourceCard.tsx:312 msgid "Remove from my feeds?" msgstr "從我的動態源中刪除?" @@ -3993,14 +3949,11 @@ msgstr "刪除搜尋紀錄中的個人檔案" msgid "Remove quote" msgstr "刪除引用貼文" -#: src/view/com/util/post-ctrls/RepostButton.tsx:92 -#: src/view/com/util/post-ctrls/RepostButton.tsx:108 #: src/view/com/util/post-ctrls/RepostButton.tsx:92 #: src/view/com/util/post-ctrls/RepostButton.tsx:108 msgid "Remove repost" msgstr "刪除轉貼貼文" -#: src/view/com/posts/FeedErrorMessage.tsx:210 #: src/view/com/posts/FeedErrorMessage.tsx:210 msgid "Remove this feed from your saved feeds" msgstr "將這個動態源從您已儲存之動態源列表中刪除" @@ -4010,7 +3963,6 @@ msgstr "將這個動態源從您已儲存之動態源列表中刪除" msgid "Removed from list" msgstr "從列表中刪除" -#: src/view/com/feeds/FeedSourceCard.tsx:139 #: src/view/com/feeds/FeedSourceCard.tsx:139 msgid "Removed from my feeds" msgstr "已從我的動態源中刪除" @@ -4042,7 +3994,7 @@ msgstr "回覆" msgid "Replies to this thread are disabled" msgstr "對此討論串的回覆已停用" -#: src/view/com/composer/Composer.tsx:503 +#: src/view/com/composer/Composer.tsx:475 msgctxt "action" msgid "Reply" msgstr "回覆" @@ -4122,9 +4074,6 @@ msgstr "檢舉這則貼文" msgid "Report this user" msgstr "檢舉這個用戶" -#: src/view/com/util/post-ctrls/RepostButton.tsx:64 -#: src/view/com/util/post-ctrls/RepostButton.tsx:93 -#: src/view/com/util/post-ctrls/RepostButton.tsx:109 #: src/view/com/util/post-ctrls/RepostButton.tsx:64 #: src/view/com/util/post-ctrls/RepostButton.tsx:93 #: src/view/com/util/post-ctrls/RepostButton.tsx:109 @@ -4137,7 +4086,6 @@ msgstr "轉貼" msgid "Repost" msgstr "轉貼" -#: src/view/com/util/post-ctrls/RepostButton.tsx:85 #: src/view/com/util/post-ctrls/RepostButton.tsx:85 #: src/view/com/util/post-ctrls/RepostButton.web.tsx:46 #: src/view/com/util/post-ctrls/RepostButton.web.tsx:92 @@ -4518,7 +4466,6 @@ msgstr "提交意見" msgid "Send message" msgstr "重送訊息" -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:59 #: src/components/dms/dialogs/ShareViaChatDialog.tsx:59 msgid "Send post to..." msgstr "傳送貼文給…" @@ -4791,9 +4738,6 @@ msgstr "在您的動態中顯示來自 {0} 的貼文" #: src/view/shell/bottom-bar/BottomBarWeb.tsx:204 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:205 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:207 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:204 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:205 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:207 #: src/view/shell/NavSignupCard.tsx:69 #: src/view/shell/NavSignupCard.tsx:70 #: src/view/shell/NavSignupCard.tsx:72 @@ -4827,9 +4771,6 @@ msgstr "登出" #: src/view/shell/bottom-bar/BottomBarWeb.tsx:194 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:195 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:197 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:194 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:195 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:197 #: src/view/shell/NavSignupCard.tsx:60 #: src/view/shell/NavSignupCard.tsx:61 #: src/view/shell/NavSignupCard.tsx:63 @@ -5107,8 +5048,8 @@ msgstr "以下標記已套用到您的內容。" msgid "The following steps will help customize your Bluesky experience." msgstr "以下步驟將幫助自訂您的 Bluesky 體驗。" -#: src/view/com/post-thread/PostThread.tsx:188 -#: src/view/com/post-thread/PostThread.tsx:200 +#: src/view/com/post-thread/PostThread.tsx:189 +#: src/view/com/post-thread/PostThread.tsx:201 msgid "The post may have been deleted." msgstr "這則貼文可能已被刪除。" @@ -5133,7 +5074,6 @@ msgstr "帳號停用沒有時間限制,隨時都可以重新啟用。" msgid "There was an an issue contacting the server, please check your internet connection and try again." msgstr "連線至伺服器時出現問題,請檢查您的網路連線並重試。" -#: src/view/com/posts/FeedErrorMessage.tsx:145 #: src/view/com/posts/FeedErrorMessage.tsx:145 msgid "There was an an issue removing this feed. Please check your internet connection and try again." msgstr "刪除動態源時出現問題,請檢查您的網路連線並重試。" @@ -5158,14 +5098,11 @@ msgstr "連線到 Tenor 時出現問題。" msgid "There was an issue contacting the server" msgstr "連線伺服器時出現問題" -#: src/view/com/feeds/FeedSourceCard.tsx:128 -#: src/view/com/feeds/FeedSourceCard.tsx:141 #: src/view/com/feeds/FeedSourceCard.tsx:128 #: src/view/com/feeds/FeedSourceCard.tsx:141 msgid "There was an issue contacting your server" msgstr "連線伺服器時出現問題" -#: src/view/com/notifications/Feed.tsx:126 #: src/view/com/notifications/Feed.tsx:126 msgid "There was an issue fetching notifications. Tap here to try again." msgstr "取得通知時發生問題,點擊這裡重試。" @@ -5264,12 +5201,11 @@ msgstr "此內容由 {0} 託管。是否要啟用外部媒體?" msgid "This content is not available because one of the users involved has blocked the other." msgstr "由於其中一個用戶封鎖了另一個用戶,無法查看此內容。" -#: src/view/com/posts/FeedErrorMessage.tsx:114 #: src/view/com/posts/FeedErrorMessage.tsx:114 msgid "This content is not viewable without a Bluesky account." msgstr "沒有 Bluesky 帳號,無法查看此內容。" -#: src/screens/Messages/List/ChatListItem.tsx:211 +#: src/screens/Messages/List/ChatListItem.tsx:213 msgid "This conversation is with a deleted or a deactivated account. Press for options." msgstr "此對話是與已刪除或停用的帳號進行的。點擊以查看選項。" @@ -5277,7 +5213,6 @@ msgstr "此對話是與已刪除或停用的帳號進行的。點擊以查看選 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost." msgstr "此功能目前為測試版本。您可以在<0>這篇部落格文章中瞭解更多有關資訊。" -#: src/view/com/posts/FeedErrorMessage.tsx:120 #: src/view/com/posts/FeedErrorMessage.tsx:120 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." msgstr "此動態源由於目前使用人數眾多而暫時無法使用。請稍後再試。" @@ -5517,7 +5452,6 @@ msgstr "解除封鎖帳號" msgid "Unblock Account?" msgstr "解除封鎖?" -#: src/view/com/util/post-ctrls/RepostButton.tsx:63 #: src/view/com/util/post-ctrls/RepostButton.tsx:63 #: src/view/com/util/post-ctrls/RepostButton.web.tsx:69 #: src/view/com/util/post-ctrls/RepostButton.web.tsx:73 @@ -5615,7 +5549,6 @@ msgstr "更新至 {handle}" msgid "Updating..." msgstr "更新中…" -#: src/screens/Onboarding/StepProfile/index.tsx:281 #: src/screens/Onboarding/StepProfile/index.tsx:281 msgid "Upload a photo instead" msgstr "或是上傳圖片" @@ -5837,7 +5770,6 @@ msgstr "查看有關這些標記的資訊" #: src/components/ProfileHoverCard/index.web.tsx:429 #: src/view/com/posts/AviFollowButton.tsx:58 #: src/view/com/posts/FeedErrorMessage.tsx:174 -#: src/view/com/posts/FeedErrorMessage.tsx:174 msgid "View profile" msgstr "查看資料" @@ -5856,7 +5788,7 @@ msgstr "查看喜歡此動態源的用戶" #: src/view/com/home/HomeHeaderLayout.web.tsx:78 #: src/view/com/home/HomeHeaderLayoutMobile.tsx:84 msgid "View your feeds and explore more" -msgstr "" +msgstr "查看您的動態並探索更多內容" #: src/view/com/modals/LinkWarning.tsx:89 #: src/view/com/modals/LinkWarning.tsx:95 @@ -5941,7 +5873,7 @@ msgstr "很抱歉,我們目前無法載入您的靜音文字。請稍後再試 msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." msgstr "很抱歉,無法完成您的搜尋請求。請稍後再試。" -#: src/view/com/composer/Composer.tsx:311 +#: src/view/com/composer/Composer.tsx:318 msgid "We're sorry! The post you are replying to has been deleted." msgstr "很抱歉!您回覆的貼文已被刪除。" @@ -5964,7 +5896,7 @@ msgstr "您感興趣的是什麼?" #: src/view/com/auth/SplashScreen.tsx:40 #: src/view/com/auth/SplashScreen.web.tsx:86 -#: src/view/com/composer/Composer.tsx:352 +#: src/view/com/composer/Composer.tsx:359 msgid "What's up?" msgstr "發生了什麼新鮮事?" @@ -6023,11 +5955,11 @@ msgstr "寬" msgid "Write a message" msgstr "撰寫訊息" -#: src/view/com/composer/Composer.tsx:581 +#: src/view/com/composer/Composer.tsx:549 msgid "Write post" msgstr "撰寫貼文" -#: src/view/com/composer/Composer.tsx:351 +#: src/view/com/composer/Composer.tsx:358 #: src/view/com/composer/Prompt.tsx:39 msgid "Write your reply" msgstr "撰寫您的回覆" @@ -6099,7 +6031,7 @@ msgstr "您沒有任何跟隨者。" #: src/screens/Profile/KnownFollowers.tsx:99 msgid "You don't follow any users who follow @{name}." -msgstr "" +msgstr "您沒有跟隨任何也跟隨 @{name} 之用戶。" #: 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." @@ -6113,7 +6045,7 @@ msgstr "您目前還沒有任何釘選的動態源。" msgid "You don't have any saved feeds." msgstr "您目前還沒有任何已儲存的動態源。" -#: src/view/com/post-thread/PostThread.tsx:194 +#: src/view/com/post-thread/PostThread.tsx:195 msgid "You have blocked the author or you have been blocked by the author." msgstr "您已封鎖該作者,或您已被該作者封鎖。" @@ -6310,7 +6242,7 @@ msgstr "您的靜音文字" msgid "Your password has been changed successfully!" msgstr "您的密碼已成功更改!" -#: src/view/com/composer/Composer.tsx:342 +#: src/view/com/composer/Composer.tsx:349 msgid "Your post has been published" msgstr "您的貼文已發佈" @@ -6326,7 +6258,7 @@ msgstr "您的個人檔案" msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in." msgstr "其他 Bluesky 用戶將無法再看到您的個人檔案、貼文、動態和列表。您可以隨時登入以重新啟用您的帳號。" -#: src/view/com/composer/Composer.tsx:341 +#: src/view/com/composer/Composer.tsx:348 msgid "Your reply has been published" msgstr "您的回覆已發佈" From 004b4e39698909e5a645fb1c28dcee45cc80ea15 Mon Sep 17 00:00:00 2001 From: Kuwa Lee Date: Wed, 12 Jun 2024 09:02:21 +0800 Subject: [PATCH 002/127] BOTH: Fix msgid "Deleted post." --- src/locale/locales/zh-CN/messages.po | 4 ++-- src/locale/locales/zh-TW/messages.po | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/locale/locales/zh-CN/messages.po b/src/locale/locales/zh-CN/messages.po index 3646c566ed..cf1bf2d5b1 100644 --- a/src/locale/locales/zh-CN/messages.po +++ b/src/locale/locales/zh-CN/messages.po @@ -8,7 +8,7 @@ msgstr "" "Language: zh_CN\n" "Project-Id-Version: zh-CN for bluesky-social-app\n" "Report-Msgid-Bugs-To: Frudrax Cheng \n" -"PO-Revision-Date: 2024-06-11 16:23+0800\n" +"PO-Revision-Date: 2024-06-12 09:01+0800\n" "Last-Translator: Frudrax Cheng \n" "Language-Team: Frudrax Cheng (auroursa), Simon Chan (RitsukiP), U2FsdGVkX1, Mikan Harada (mitian233), IceCodeNew\n" "Plural-Forms: \n" @@ -1360,7 +1360,7 @@ msgstr "已删除" #: src/view/com/post-thread/PostThread.tsx:348 msgid "Deleted post." -msgstr "已删除帖子。" +msgstr "已删除的帖子。" #: src/view/screens/Settings/index.tsx:891 msgid "Deletes the chat declaration record" diff --git a/src/locale/locales/zh-TW/messages.po b/src/locale/locales/zh-TW/messages.po index 5cb9ccfbb4..dcb064dceb 100644 --- a/src/locale/locales/zh-TW/messages.po +++ b/src/locale/locales/zh-TW/messages.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: zh-TW for bluesky-social-app\n" "POT-Creation-Date: \n" "Report-Msgid-Bugs-To: Kuwa Lee , Frudrax Cheng \n" -"PO-Revision-Date: 2024-06-12 08:47+0800\n" +"PO-Revision-Date: 2024-06-12 09:00+0800\n" "Last-Translator: \n" "Language-Team: Frudrax Cheng , Kuwa Lee , noeFly, snowleo208, Kisaragi Hiu, Yi-Jyun Pan, toto6038, cirx1e\n" "Language: zh_TW\n" @@ -1351,7 +1351,7 @@ msgstr "已刪除" #: src/view/com/post-thread/PostThread.tsx:349 msgid "Deleted post." -msgstr "已刪除貼文。" +msgstr "已刪除的貼文。" #: src/view/screens/Settings/index.tsx:891 msgid "Deletes the chat declaration record" From 23a14454dc8085b761ef7ae062aaebf4c4c3917c Mon Sep 17 00:00:00 2001 From: Kuwa Lee Date: Fri, 14 Jun 2024 01:45:52 +0800 Subject: [PATCH 003/127] TW: Update and clean --- src/locale/locales/zh-TW/messages.po | 284 ++++++++++++++------------- 1 file changed, 146 insertions(+), 138 deletions(-) diff --git a/src/locale/locales/zh-TW/messages.po b/src/locale/locales/zh-TW/messages.po index dcb064dceb..227d5956af 100644 --- a/src/locale/locales/zh-TW/messages.po +++ b/src/locale/locales/zh-TW/messages.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: zh-TW for bluesky-social-app\n" "POT-Creation-Date: \n" "Report-Msgid-Bugs-To: Kuwa Lee , Frudrax Cheng \n" -"PO-Revision-Date: 2024-06-12 09:00+0800\n" +"PO-Revision-Date: 2024-06-14 01:45+0800\n" "Last-Translator: \n" "Language-Team: Frudrax Cheng , Kuwa Lee , noeFly, snowleo208, Kisaragi Hiu, Yi-Jyun Pan, toto6038, cirx1e\n" "Language: zh_TW\n" @@ -37,21 +37,17 @@ msgstr "{0, plural, one {該內容有 # 個標記} other {該內容有 # 個標 msgid "{0, plural, one {# repost} other {# reposts}}" msgstr "{0, plural, one {# 個轉貼} other {# 個轉貼}}" -#: src/components/KnownFollowers.tsx:179 -msgid "{0, plural, one {and # other} other {and # others}}" -msgstr "{0, plural, one {和其他 # 人} other {和其他 # 人}}" - -#: src/components/ProfileHoverCard/index.web.tsx:376 +#: src/components/ProfileHoverCard/index.web.tsx:385 #: src/screens/Profile/Header/Metrics.tsx:23 msgid "{0, plural, one {follower} other {followers}}" msgstr "{0, plural, one {個跟隨者} other {個跟隨者}}" -#: src/components/ProfileHoverCard/index.web.tsx:380 +#: src/components/ProfileHoverCard/index.web.tsx:389 #: src/screens/Profile/Header/Metrics.tsx:27 msgid "{0, plural, one {following} other {following}}" msgstr "{0, plural, one {個跟隨中} other {個跟隨中}}" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:252 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:254 msgid "{0, plural, one {Like (# like)} other {Like (# likes)}}" msgstr "{0, plural, one {喜歡(# 個喜歡)} other {喜歡(# 個喜歡)}}" @@ -67,7 +63,7 @@ msgstr "{0, plural,one {# 個用戶已喜歡} other {# 個用戶已喜歡}}" msgid "{0, plural, one {post} other {posts}}" msgstr "{0, plural, one {則貼文} other {則貼文}}" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:210 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:212 msgid "{0, plural, one {Reply (# reply)} other {Reply (# replies)}}" msgstr "{0, plural, one {回覆(# 個回覆)} other {回覆(# 個回覆)}}" @@ -75,7 +71,7 @@ msgstr "{0, plural, one {回覆(# 個回覆)} other {回覆(# 個回覆) msgid "{0, plural, one {repost} other {reposts}}" msgstr "{0, plural, one {轉貼} other {轉貼}}" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:248 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:250 msgid "{0, plural, one {Unlike (# like)} other {Unlike (# likes)}}" msgstr "{0, plural, one {撤回喜歡(# 個喜歡)} other {撤回喜歡(# 個喜歡)}}" @@ -95,7 +91,7 @@ msgstr "{estimatedTimeHrs, plural, one {時} other {時}}" msgid "{estimatedTimeMins, plural, one {minute} other {minutes}}" msgstr "{estimatedTimeMins, plural, one {分} other {分}}" -#: src/components/ProfileHoverCard/index.web.tsx:457 +#: src/components/ProfileHoverCard/index.web.tsx:490 #: src/screens/Profile/Header/Metrics.tsx:50 msgid "{following} following" msgstr "{following} 個跟隨中" @@ -171,15 +167,15 @@ msgstr "無障礙設定" msgid "Account" msgstr "帳號" -#: src/view/com/profile/ProfileMenu.tsx:142 +#: src/view/com/profile/ProfileMenu.tsx:145 msgid "Account blocked" msgstr "已封鎖帳號" -#: src/view/com/profile/ProfileMenu.tsx:156 +#: src/view/com/profile/ProfileMenu.tsx:159 msgid "Account followed" msgstr "已跟隨帳號" -#: src/view/com/profile/ProfileMenu.tsx:116 +#: src/view/com/profile/ProfileMenu.tsx:119 msgid "Account muted" msgstr "已靜音帳號" @@ -200,16 +196,16 @@ msgstr "帳號選項" msgid "Account removed from quick access" msgstr "已從快速存取中移除帳號" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:135 -#: src/view/com/profile/ProfileMenu.tsx:131 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:139 +#: src/view/com/profile/ProfileMenu.tsx:134 msgid "Account unblocked" msgstr "已解除封鎖帳號" -#: src/view/com/profile/ProfileMenu.tsx:169 +#: src/view/com/profile/ProfileMenu.tsx:172 msgid "Account unfollowed" msgstr "已取消跟隨帳號" -#: src/view/com/profile/ProfileMenu.tsx:105 +#: src/view/com/profile/ProfileMenu.tsx:108 msgid "Account unmuted" msgstr "已取消靜音帳號" @@ -270,8 +266,8 @@ msgstr "新增預設的「Following」動態源,它只會顯示您跟隨的人 msgid "Add the following DNS record to your domain:" msgstr "將以下 DNS 記錄新增到您的網域:" -#: src/view/com/profile/ProfileMenu.tsx:265 #: src/view/com/profile/ProfileMenu.tsx:268 +#: src/view/com/profile/ProfileMenu.tsx:271 msgid "Add to Lists" msgstr "新增至列表" @@ -379,7 +375,6 @@ msgstr "出現問題,請再試一次。" msgid "an unknown error occurred" msgstr "出現未知錯誤" -#: src/components/KnownFollowers.tsx:187 #: src/view/com/notifications/FeedItem.tsx:258 #: src/view/com/threadgate/WhoCanReply.tsx:180 msgid "and" @@ -469,7 +464,7 @@ msgstr "您確定要離開此對話嗎?您的訊息將為您刪除,但不會 msgid "Are you sure you want to remove {0} from your feeds?" msgstr "您確定要從您的動態中移除 {0} 嗎?" -#: src/view/com/composer/Composer.tsx:630 +#: src/view/com/composer/Composer.tsx:632 msgid "Are you sure you'd like to discard this draft?" msgstr "您確定要捨棄此草稿嗎?" @@ -524,8 +519,8 @@ msgstr "生日" msgid "Birthday:" msgstr "生日:" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:309 -#: src/view/com/profile/ProfileMenu.tsx:363 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:314 +#: src/view/com/profile/ProfileMenu.tsx:366 msgid "Block" msgstr "封鎖" @@ -534,12 +529,12 @@ msgstr "封鎖" msgid "Block account" msgstr "封鎖帳號" -#: src/view/com/profile/ProfileMenu.tsx:302 -#: src/view/com/profile/ProfileMenu.tsx:309 +#: src/view/com/profile/ProfileMenu.tsx:305 +#: src/view/com/profile/ProfileMenu.tsx:312 msgid "Block Account" msgstr "封鎖帳號" -#: src/view/com/profile/ProfileMenu.tsx:346 +#: src/view/com/profile/ProfileMenu.tsx:349 msgid "Block Account?" msgstr "封鎖帳號?" @@ -569,7 +564,7 @@ msgstr "已封鎖帳號" msgid "Blocked Accounts" msgstr "已封鎖帳號" -#: src/view/com/profile/ProfileMenu.tsx:358 +#: src/view/com/profile/ProfileMenu.tsx:361 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." msgstr "被封鎖的帳號無法在您的討論串中回覆、提及您,或以其他方式與您互動。" @@ -589,7 +584,7 @@ msgstr "封鎖此帳號不會阻止被貼上標記。" msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." msgstr "封鎖資訊是公開的。被封鎖的帳號無法在您的討論串中回覆、提及您,或以其他方式與您互動。" -#: src/view/com/profile/ProfileMenu.tsx:355 +#: src/view/com/profile/ProfileMenu.tsx:358 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 "封鎖此帳號不會阻止被貼上標記,但它會阻止此帳號在您的討論串中回覆或與您進行互動。" @@ -664,8 +659,8 @@ msgstr "只能包含字母、數字、空格、破折號及底線。長度必須 #: src/components/Prompt.tsx:121 #: src/components/TagMenu/index.tsx:268 #: src/screens/Deactivated.tsx:161 -#: src/view/com/composer/Composer.tsx:432 -#: src/view/com/composer/Composer.tsx:438 +#: src/view/com/composer/Composer.tsx:434 +#: src/view/com/composer/Composer.tsx:440 #: src/view/com/modals/ChangeEmail.tsx:213 #: src/view/com/modals/ChangeEmail.tsx:215 #: src/view/com/modals/ChangeHandle.tsx:148 @@ -944,7 +939,7 @@ msgstr "關閉底部導覽列" msgid "Closes password update alert" msgstr "關閉密碼更新警告" -#: src/view/com/composer/Composer.tsx:434 +#: src/view/com/composer/Composer.tsx:436 msgid "Closes post composer and discards post draft" msgstr "關閉貼文編輯頁並捨棄草稿" @@ -981,7 +976,7 @@ msgstr "完成初始設定並開始使用您的帳號" msgid "Complete the challenge" msgstr "完成驗證" -#: src/view/com/composer/Composer.tsx:551 +#: src/view/com/composer/Composer.tsx:553 msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" msgstr "撰寫貼文的長度最多為 {MAX_GRAPHEME_LENGTH} 個字元" @@ -1150,8 +1145,8 @@ msgstr "複製程式碼" msgid "Copy link to list" msgstr "複製列表連結" -#: src/view/com/util/forms/PostDropdownBtn.tsx:297 -#: src/view/com/util/forms/PostDropdownBtn.tsx:306 +#: src/view/com/util/forms/PostDropdownBtn.tsx:300 +#: src/view/com/util/forms/PostDropdownBtn.tsx:309 msgid "Copy link to post" msgstr "複製貼文連結" @@ -1160,8 +1155,8 @@ msgstr "複製貼文連結" msgid "Copy message text" msgstr "複製訊息文字" -#: src/view/com/util/forms/PostDropdownBtn.tsx:275 -#: src/view/com/util/forms/PostDropdownBtn.tsx:277 +#: src/view/com/util/forms/PostDropdownBtn.tsx:278 +#: src/view/com/util/forms/PostDropdownBtn.tsx:280 msgid "Copy post text" msgstr "複製貼文文字" @@ -1281,7 +1276,7 @@ msgid "Debug panel" msgstr "偵錯面板" #: src/components/dms/MessageMenu.tsx:151 -#: src/view/com/util/forms/PostDropdownBtn.tsx:423 +#: src/view/com/util/forms/PostDropdownBtn.tsx:426 #: src/view/screens/AppPasswords.tsx:285 #: src/view/screens/ProfileList.tsx:667 msgid "Delete" @@ -1332,8 +1327,8 @@ msgstr "刪除我的帳號" msgid "Delete My Account…" msgstr "刪除我的帳號…" -#: src/view/com/util/forms/PostDropdownBtn.tsx:404 -#: src/view/com/util/forms/PostDropdownBtn.tsx:406 +#: src/view/com/util/forms/PostDropdownBtn.tsx:407 +#: src/view/com/util/forms/PostDropdownBtn.tsx:409 msgid "Delete post" msgstr "刪除貼文" @@ -1341,7 +1336,7 @@ msgstr "刪除貼文" msgid "Delete this list?" msgstr "刪除此列表?" -#: src/view/com/util/forms/PostDropdownBtn.tsx:418 +#: src/view/com/util/forms/PostDropdownBtn.tsx:421 msgid "Delete this post?" msgstr "刪除這條貼文?" @@ -1401,11 +1396,11 @@ msgstr "關閉觸覺回饋" msgid "Disabled" msgstr "停用" -#: src/view/com/composer/Composer.tsx:632 +#: src/view/com/composer/Composer.tsx:634 msgid "Discard" msgstr "捨棄" -#: src/view/com/composer/Composer.tsx:629 +#: src/view/com/composer/Composer.tsx:631 msgid "Discard draft?" msgstr "捨棄草稿?" @@ -1490,7 +1485,7 @@ msgstr "完成{extraText}" msgid "Download CAR file" msgstr "下載 CAR 檔案" -#: src/view/com/composer/text-input/TextInput.web.tsx:261 +#: src/view/com/composer/text-input/TextInput.web.tsx:272 msgid "Drop to add images" msgstr "拖放即可新增圖片" @@ -1574,12 +1569,12 @@ msgid "Edit my profile" msgstr "編輯我的個人檔案" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:181 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:175 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:179 msgid "Edit profile" msgstr "編輯個人檔案" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:184 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:178 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:182 msgid "Edit Profile" msgstr "編輯個人檔案" @@ -1634,8 +1629,8 @@ msgid "Embed HTML code" msgstr "嵌入 HTML 程式碼" #: src/components/dialogs/Embed.tsx:97 -#: src/view/com/util/forms/PostDropdownBtn.tsx:314 -#: src/view/com/util/forms/PostDropdownBtn.tsx:316 +#: src/view/com/util/forms/PostDropdownBtn.tsx:317 +#: src/view/com/util/forms/PostDropdownBtn.tsx:319 msgid "Embed post" msgstr "嵌入貼文" @@ -1960,9 +1955,9 @@ msgstr "水平翻轉" msgid "Flip vertically" msgstr "垂直翻轉" -#: src/components/ProfileHoverCard/index.web.tsx:412 -#: src/components/ProfileHoverCard/index.web.tsx:423 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:248 +#: src/components/ProfileHoverCard/index.web.tsx:433 +#: src/components/ProfileHoverCard/index.web.tsx:444 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:252 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:146 #: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:247 msgid "Follow" @@ -1973,7 +1968,7 @@ msgctxt "action" msgid "Follow" msgstr "跟隨" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:234 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:238 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:128 msgid "Follow {0}" msgstr "跟隨 {0}" @@ -1982,8 +1977,8 @@ msgstr "跟隨 {0}" msgid "Follow {name}" msgstr "跟隨 {name}" -#: src/view/com/profile/ProfileMenu.tsx:244 -#: src/view/com/profile/ProfileMenu.tsx:255 +#: src/view/com/profile/ProfileMenu.tsx:247 +#: src/view/com/profile/ProfileMenu.tsx:258 msgid "Follow Account" msgstr "跟隨帳號" @@ -1991,14 +1986,22 @@ msgstr "跟隨帳號" msgid "Follow Back" msgstr "回追蹤" -#: src/components/KnownFollowers.tsx:169 -msgid "Followed by" -msgstr "也被以下用戶跟隨:" - #: src/view/com/profile/ProfileCard.tsx:227 msgid "Followed by {0}" msgstr "由 {0} 跟隨" +#: src/components/KnownFollowers.tsx:192 +msgid "Followed by <0>{0}" +msgstr "已被你跟隨的 <0>{0} 跟隨" + +#: src/components/KnownFollowers.tsx:181 +msgid "Followed by <0>{0} and <1>{1}" +msgstr "已被你跟隨的 <0>{0} 和 <1>{1} 跟隨" + +#: src/components/KnownFollowers.tsx:168 +msgid "Followed by <0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}}" +msgstr "已被你跟隨的 <0>{0}, <1>{1}, 和 {2, plural, one {其他 # 人跟隨} other {其他 # 人跟隨}}" + #: src/view/com/modals/Threadgate.tsx:99 msgid "Followed users" msgstr "已跟隨的用戶" @@ -2025,9 +2028,9 @@ msgstr "您所認識的 @{0} 之跟隨者" msgid "Followers you know" msgstr "您也認識的跟隨者" -#: src/components/ProfileHoverCard/index.web.tsx:411 -#: src/components/ProfileHoverCard/index.web.tsx:422 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:246 +#: src/components/ProfileHoverCard/index.web.tsx:432 +#: src/components/ProfileHoverCard/index.web.tsx:443 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:250 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:149 #: src/view/com/profile/ProfileFollows.tsx:104 #: src/view/screens/Feeds.tsx:656 @@ -2036,7 +2039,7 @@ msgstr "您也認識的跟隨者" msgid "Following" msgstr "跟隨中" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:94 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:98 msgid "Following {0}" msgstr "已跟隨 {0}" @@ -2223,7 +2226,7 @@ msgstr "這是您的應用程式專用密碼。" #: src/lib/moderation/useLabelBehaviorDescription.ts:20 #: src/lib/moderation/useLabelBehaviorDescription.ts:25 #: src/lib/moderation/useLabelBehaviorDescription.ts:30 -#: src/view/com/util/forms/PostDropdownBtn.tsx:432 +#: src/view/com/util/forms/PostDropdownBtn.tsx:435 msgid "Hide" msgstr "隱藏" @@ -2232,8 +2235,8 @@ msgctxt "action" msgid "Hide" msgstr "隱藏" -#: src/view/com/util/forms/PostDropdownBtn.tsx:377 -#: src/view/com/util/forms/PostDropdownBtn.tsx:379 +#: src/view/com/util/forms/PostDropdownBtn.tsx:380 +#: src/view/com/util/forms/PostDropdownBtn.tsx:382 msgid "Hide post" msgstr "隱藏貼文" @@ -2242,7 +2245,7 @@ msgstr "隱藏貼文" msgid "Hide the content" msgstr "隱藏內容" -#: src/view/com/util/forms/PostDropdownBtn.tsx:429 +#: src/view/com/util/forms/PostDropdownBtn.tsx:432 msgid "Hide this post?" msgstr "隱藏這則貼文?" @@ -2336,7 +2339,7 @@ msgstr "如果根據您所在國家的法律,您尚未成年,則您的父母 msgid "If you delete this list, you won't be able to recover it." msgstr "如果刪除這個列表,您將無法恢復它。" -#: src/view/com/util/forms/PostDropdownBtn.tsx:420 +#: src/view/com/util/forms/PostDropdownBtn.tsx:423 msgid "If you remove this post, you won't be able to recover it." msgstr "如果刪除這則貼文,您將無法恢復它。" @@ -2863,8 +2866,8 @@ msgstr "靜音" msgid "Mute {truncatedTag}" msgstr "靜音 {truncatedTag}" -#: src/view/com/profile/ProfileMenu.tsx:281 -#: src/view/com/profile/ProfileMenu.tsx:288 +#: src/view/com/profile/ProfileMenu.tsx:284 +#: src/view/com/profile/ProfileMenu.tsx:291 msgid "Mute Account" msgstr "靜音帳號" @@ -2905,13 +2908,13 @@ msgstr "在貼文內容和話題標籤中隱藏該文字" msgid "Mute this word in tags only" msgstr "僅在話題標籤中隱藏該文字" -#: src/view/com/util/forms/PostDropdownBtn.tsx:352 -#: src/view/com/util/forms/PostDropdownBtn.tsx:358 +#: src/view/com/util/forms/PostDropdownBtn.tsx:355 +#: src/view/com/util/forms/PostDropdownBtn.tsx:361 msgid "Mute thread" msgstr "靜音討論串" -#: src/view/com/util/forms/PostDropdownBtn.tsx:368 -#: src/view/com/util/forms/PostDropdownBtn.tsx:370 +#: src/view/com/util/forms/PostDropdownBtn.tsx:371 +#: src/view/com/util/forms/PostDropdownBtn.tsx:373 msgid "Mute words & tags" msgstr "靜音文字和標籤" @@ -3108,7 +3111,7 @@ msgstr "無 DNS 控制台" msgid "No featured GIFs found. There may be an issue with Tenor." msgstr "未找到精選 GIF,Tenor 可能發生問題。" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:116 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:120 msgid "No longer following {0}" msgstr "不再跟隨 {0}" @@ -3199,9 +3202,9 @@ msgstr "未找到" msgid "Not right now" msgstr "暫時不需要" -#: src/view/com/profile/ProfileMenu.tsx:370 -#: src/view/com/util/forms/PostDropdownBtn.tsx:446 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:308 +#: src/view/com/profile/ProfileMenu.tsx:373 +#: src/view/com/util/forms/PostDropdownBtn.tsx:449 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:310 msgid "Note about sharing" msgstr "關於分享的注意事項" @@ -3273,7 +3276,7 @@ msgstr "最舊的回覆優先" msgid "Onboarding reset" msgstr "重新開始引導流程" -#: src/view/com/composer/Composer.tsx:503 +#: src/view/com/composer/Composer.tsx:505 msgid "One or more images is missing alt text." msgstr "至少有一張圖片缺失了替代文字。" @@ -3316,8 +3319,8 @@ msgstr "開啟頭像建立工具" msgid "Open conversation options" msgstr "開啟對話選項" -#: src/view/com/composer/Composer.tsx:613 -#: src/view/com/composer/Composer.tsx:614 +#: src/view/com/composer/Composer.tsx:615 +#: src/view/com/composer/Composer.tsx:616 msgid "Open emoji picker" msgstr "開啟表情符號選擇器" @@ -3341,7 +3344,7 @@ msgstr "開啟靜音文字和標籤設定" msgid "Open navigation" msgstr "開啟導覽" -#: src/view/com/util/forms/PostDropdownBtn.tsx:237 +#: src/view/com/util/forms/PostDropdownBtn.tsx:240 msgid "Open post options menu" msgstr "開啟貼文選項選單" @@ -3678,8 +3681,8 @@ msgstr "政治" msgid "Porn" msgstr "色情內容" -#: src/view/com/composer/Composer.tsx:477 -#: src/view/com/composer/Composer.tsx:485 +#: src/view/com/composer/Composer.tsx:479 +#: src/view/com/composer/Composer.tsx:487 msgctxt "action" msgid "Post" msgstr "發佈" @@ -3765,7 +3768,7 @@ msgstr "按下以更改託管服務供應商" msgid "Press to retry" msgstr "按下以重試" -#: src/components/KnownFollowers.tsx:111 +#: src/components/KnownFollowers.tsx:108 msgid "Press to view followers of this account that you also follow" msgstr "按下以查看被您跟隨且跟隨此帳戶者" @@ -3835,11 +3838,11 @@ msgstr "公開且可共享的批量靜音或封鎖列表。" msgid "Public, shareable lists which can drive feeds." msgstr "公開且可共享的列表,可作為動態源使用。" -#: src/view/com/composer/Composer.tsx:462 +#: src/view/com/composer/Composer.tsx:464 msgid "Publish post" msgstr "發佈貼文" -#: src/view/com/composer/Composer.tsx:462 +#: src/view/com/composer/Composer.tsx:464 msgid "Publish reply" msgstr "發佈回覆" @@ -3994,7 +3997,7 @@ msgstr "回覆" msgid "Replies to this thread are disabled" msgstr "對此討論串的回覆已停用" -#: src/view/com/composer/Composer.tsx:475 +#: src/view/com/composer/Composer.tsx:477 msgctxt "action" msgid "Reply" msgstr "回覆" @@ -4015,8 +4018,8 @@ msgstr "對 <0><1/> 回覆" msgid "Report" msgstr "檢舉" -#: src/view/com/profile/ProfileMenu.tsx:321 #: src/view/com/profile/ProfileMenu.tsx:324 +#: src/view/com/profile/ProfileMenu.tsx:327 msgid "Report Account" msgstr "檢舉帳號" @@ -4043,8 +4046,8 @@ msgstr "檢舉列表" msgid "Report message" msgstr "檢舉訊息" -#: src/view/com/util/forms/PostDropdownBtn.tsx:394 -#: src/view/com/util/forms/PostDropdownBtn.tsx:396 +#: src/view/com/util/forms/PostDropdownBtn.tsx:397 +#: src/view/com/util/forms/PostDropdownBtn.tsx:399 msgid "Report post" msgstr "檢舉貼文" @@ -4486,8 +4489,8 @@ msgstr "將檢舉提交至 {0}" msgid "Send verification email" msgstr "發送驗證電子郵件" -#: src/view/com/util/forms/PostDropdownBtn.tsx:286 #: src/view/com/util/forms/PostDropdownBtn.tsx:289 +#: src/view/com/util/forms/PostDropdownBtn.tsx:292 msgid "Send via direct message" msgstr "透過私人訊息發送" @@ -4592,11 +4595,11 @@ msgctxt "action" msgid "Share" msgstr "分享" -#: src/view/com/profile/ProfileMenu.tsx:217 -#: src/view/com/profile/ProfileMenu.tsx:226 -#: src/view/com/util/forms/PostDropdownBtn.tsx:297 -#: src/view/com/util/forms/PostDropdownBtn.tsx:306 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:297 +#: src/view/com/profile/ProfileMenu.tsx:220 +#: src/view/com/profile/ProfileMenu.tsx:229 +#: src/view/com/util/forms/PostDropdownBtn.tsx:300 +#: src/view/com/util/forms/PostDropdownBtn.tsx:309 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:299 #: src/view/screens/ProfileList.tsx:428 msgid "Share" msgstr "分享" @@ -4609,9 +4612,9 @@ msgstr "分享一個有趣的故事!" msgid "Share a fun fact!" msgstr "分享一個趣聞!📰" -#: src/view/com/profile/ProfileMenu.tsx:375 -#: src/view/com/util/forms/PostDropdownBtn.tsx:451 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:313 +#: src/view/com/profile/ProfileMenu.tsx:378 +#: src/view/com/util/forms/PostDropdownBtn.tsx:454 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:315 msgid "Share anyway" msgstr "仍然分享" @@ -4658,7 +4661,7 @@ msgstr "顯示標記" msgid "Show badge and filter from feeds" msgstr "顯示標記並從動態源中篩選" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:211 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:215 msgid "Show follows similar to {0}" msgstr "顯示類似於 {0} 的跟隨者" @@ -4666,8 +4669,8 @@ msgstr "顯示類似於 {0} 的跟隨者" msgid "Show hidden replies" msgstr "顯示隱藏回覆" -#: src/view/com/util/forms/PostDropdownBtn.tsx:336 -#: src/view/com/util/forms/PostDropdownBtn.tsx:338 +#: src/view/com/util/forms/PostDropdownBtn.tsx:339 +#: src/view/com/util/forms/PostDropdownBtn.tsx:341 msgid "Show less like this" msgstr "減少顯示此類內容" @@ -4677,8 +4680,8 @@ msgstr "減少顯示此類內容" msgid "Show More" msgstr "顯示更多" -#: src/view/com/util/forms/PostDropdownBtn.tsx:328 -#: src/view/com/util/forms/PostDropdownBtn.tsx:330 +#: src/view/com/util/forms/PostDropdownBtn.tsx:331 +#: src/view/com/util/forms/PostDropdownBtn.tsx:333 msgid "Show more like this" msgstr "顯示更多此類內容" @@ -5019,8 +5022,8 @@ msgstr "其中包含以下內容:" msgid "That handle is already taken." msgstr "這個帳號代碼已被使用。" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:305 -#: src/view/com/profile/ProfileMenu.tsx:351 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:310 +#: src/view/com/profile/ProfileMenu.tsx:354 msgid "The account will be able to interact with you after unblocking." msgstr "解除封鎖後,該帳號將能夠與您互動。" @@ -5129,17 +5132,17 @@ msgstr "提交您的檢舉時出現問題,請檢查您的網路連線。" msgid "There was an issue with fetching your app passwords" msgstr "取得應用程式專用密碼時發生問題" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:103 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:125 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:139 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:107 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:129 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:143 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:99 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:111 -#: src/view/com/profile/ProfileMenu.tsx:109 -#: src/view/com/profile/ProfileMenu.tsx:120 -#: src/view/com/profile/ProfileMenu.tsx:135 -#: src/view/com/profile/ProfileMenu.tsx:146 -#: src/view/com/profile/ProfileMenu.tsx:160 -#: src/view/com/profile/ProfileMenu.tsx:173 +#: src/view/com/profile/ProfileMenu.tsx:112 +#: src/view/com/profile/ProfileMenu.tsx:123 +#: src/view/com/profile/ProfileMenu.tsx:138 +#: src/view/com/profile/ProfileMenu.tsx:149 +#: src/view/com/profile/ProfileMenu.tsx:163 +#: src/view/com/profile/ProfileMenu.tsx:176 msgid "There was an issue! {0}" msgstr "發生問題!{0}" @@ -5274,16 +5277,16 @@ msgstr "此名稱已被使用" msgid "This post has been deleted." msgstr "這則貼文已被刪除。" -#: src/view/com/util/forms/PostDropdownBtn.tsx:448 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:310 +#: src/view/com/util/forms/PostDropdownBtn.tsx:451 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:312 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:430 +#: src/view/com/util/forms/PostDropdownBtn.tsx:433 msgid "This post will be hidden from feeds." msgstr "這則貼文將從動態隱藏。" -#: src/view/com/profile/ProfileMenu.tsx:372 +#: src/view/com/profile/ProfileMenu.tsx:375 msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't logged in." msgstr "只有登入用戶能見到此個人檔案。 未登入的人將看不到它。" @@ -5382,8 +5385,8 @@ msgstr "轉換" #: src/components/dms/MessageMenu.tsx:105 #: src/view/com/post-thread/PostThreadItem.tsx:674 #: src/view/com/post-thread/PostThreadItem.tsx:676 -#: src/view/com/util/forms/PostDropdownBtn.tsx:267 -#: src/view/com/util/forms/PostDropdownBtn.tsx:269 +#: src/view/com/util/forms/PostDropdownBtn.tsx:270 +#: src/view/com/util/forms/PostDropdownBtn.tsx:272 msgid "Translate" msgstr "翻譯" @@ -5425,14 +5428,14 @@ msgstr "無法連線到服務,請檢查您的網路連線。" #: src/components/dms/MessagesListBlockedFooter.tsx:96 #: src/components/dms/MessagesListBlockedFooter.tsx:104 #: src/components/dms/MessagesListBlockedFooter.tsx:111 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:188 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:309 -#: src/view/com/profile/ProfileMenu.tsx:363 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:192 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:314 +#: src/view/com/profile/ProfileMenu.tsx:366 #: src/view/screens/ProfileList.tsx:626 msgid "Unblock" msgstr "解除封鎖" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:193 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:197 msgctxt "action" msgid "Unblock" msgstr "解除封鎖" @@ -5442,13 +5445,13 @@ msgstr "解除封鎖" msgid "Unblock account" msgstr "解除封鎖帳號" -#: src/view/com/profile/ProfileMenu.tsx:301 -#: src/view/com/profile/ProfileMenu.tsx:307 +#: src/view/com/profile/ProfileMenu.tsx:304 +#: src/view/com/profile/ProfileMenu.tsx:310 msgid "Unblock Account" msgstr "解除封鎖帳號" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:303 -#: src/view/com/profile/ProfileMenu.tsx:345 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:308 +#: src/view/com/profile/ProfileMenu.tsx:348 msgid "Unblock Account?" msgstr "解除封鎖?" @@ -5467,12 +5470,12 @@ msgstr "取消跟隨" msgid "Unfollow" msgstr "取消跟隨" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:233 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:237 msgid "Unfollow {0}" msgstr "取消跟隨 {0}" -#: src/view/com/profile/ProfileMenu.tsx:243 -#: src/view/com/profile/ProfileMenu.tsx:253 +#: src/view/com/profile/ProfileMenu.tsx:246 +#: src/view/com/profile/ProfileMenu.tsx:256 msgid "Unfollow Account" msgstr "取消跟隨" @@ -5489,8 +5492,8 @@ msgstr "取消靜音" msgid "Unmute {truncatedTag}" msgstr "取消靜音 {truncatedTag}" -#: src/view/com/profile/ProfileMenu.tsx:280 -#: src/view/com/profile/ProfileMenu.tsx:286 +#: src/view/com/profile/ProfileMenu.tsx:283 +#: src/view/com/profile/ProfileMenu.tsx:289 msgid "Unmute Account" msgstr "取消靜音帳號" @@ -5502,8 +5505,8 @@ msgstr "取消對所有 {displayTag} 貼文的靜音" msgid "Unmute conversation" msgstr "取消靜音對話" -#: src/view/com/util/forms/PostDropdownBtn.tsx:352 -#: src/view/com/util/forms/PostDropdownBtn.tsx:357 +#: src/view/com/util/forms/PostDropdownBtn.tsx:355 +#: src/view/com/util/forms/PostDropdownBtn.tsx:360 msgid "Unmute thread" msgstr "取消靜音討論串" @@ -5746,6 +5749,10 @@ msgstr "查看 {0} 的頭像" msgid "View {0}'s profile" msgstr "查看 {0} 的個人檔案" +#: src/components/ProfileHoverCard/index.web.tsx:417 +msgid "View blocked user's profile" +msgstr "查看已封鎖用戶的個人檔案" + #: src/view/screens/Log.tsx:52 msgid "View debug entry" msgstr "查看偵錯項目" @@ -5766,8 +5773,9 @@ msgstr "查看整個討論串" msgid "View information about these labels" msgstr "查看有關這些標記的資訊" -#: src/components/ProfileHoverCard/index.web.tsx:396 -#: src/components/ProfileHoverCard/index.web.tsx:429 +#: src/components/ProfileHoverCard/index.web.tsx:405 +#: src/components/ProfileHoverCard/index.web.tsx:423 +#: src/components/ProfileHoverCard/index.web.tsx:450 #: src/view/com/posts/AviFollowButton.tsx:58 #: src/view/com/posts/FeedErrorMessage.tsx:174 msgid "View profile" @@ -5955,7 +5963,7 @@ msgstr "寬" msgid "Write a message" msgstr "撰寫訊息" -#: src/view/com/composer/Composer.tsx:549 +#: src/view/com/composer/Composer.tsx:551 msgid "Write post" msgstr "撰寫貼文" From 3ea3b5a4d4c50ca96619c0885c30bf276cac4ab0 Mon Sep 17 00:00:00 2001 From: Kuwa Lee Date: Fri, 14 Jun 2024 01:57:55 +0800 Subject: [PATCH 004/127] TW: hot fix --- src/locale/locales/zh-TW/messages.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locale/locales/zh-TW/messages.po b/src/locale/locales/zh-TW/messages.po index 227d5956af..6c30cf5ec3 100644 --- a/src/locale/locales/zh-TW/messages.po +++ b/src/locale/locales/zh-TW/messages.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: zh-TW for bluesky-social-app\n" "POT-Creation-Date: \n" "Report-Msgid-Bugs-To: Kuwa Lee , Frudrax Cheng \n" -"PO-Revision-Date: 2024-06-14 01:45+0800\n" +"PO-Revision-Date: 2024-06-14 01:57+0800\n" "Last-Translator: \n" "Language-Team: Frudrax Cheng , Kuwa Lee , noeFly, snowleo208, Kisaragi Hiu, Yi-Jyun Pan, toto6038, cirx1e\n" "Language: zh_TW\n" @@ -2000,7 +2000,7 @@ msgstr "已被你跟隨的 <0>{0} 和 <1>{1} 跟隨" #: src/components/KnownFollowers.tsx:168 msgid "Followed by <0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}}" -msgstr "已被你跟隨的 <0>{0}, <1>{1}, 和 {2, plural, one {其他 # 人跟隨} other {其他 # 人跟隨}}" +msgstr "已被你跟隨的 <0>{0}, <1>{1} 和{2, plural, one {其他 # 人跟隨} other {其他 # 人跟隨}}" #: src/view/com/modals/Threadgate.tsx:99 msgid "Followed users" From 4c0f0378808410513558696a12a68bbd3c1d9a75 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 14 Jun 2024 11:56:29 -0500 Subject: [PATCH 005/127] Reuse overfetching for popular feeds, add in existing filtering (#4501) --- src/state/queries/feed.ts | 100 +++++++++++++++++++++++++++++++++++-- src/view/screens/Feeds.tsx | 46 +++-------------- 2 files changed, 101 insertions(+), 45 deletions(-) diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index b599ac1a0f..fed23f5b12 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -1,3 +1,4 @@ +import {useCallback, useEffect, useMemo, useRef} from 'react' import { AppBskyActorDefs, AppBskyFeedDefs, @@ -171,28 +172,117 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) { }) } -export const useGetPopularFeedsQueryKey = ['getPopularFeeds'] +// HACK +// the protocol doesn't yet tell us which feeds are personalized +// this list is used to filter out feed recommendations from logged out users +// for the ones we know need it +// -prf +export const KNOWN_AUTHED_ONLY_FEEDS = [ + 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends', // popular with friends, by bsky.app + 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/mutuals', // mutuals, by skyfeed + 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/only-posts', // only posts, by skyfeed + 'at://did:plc:wzsilnxf24ehtmmc3gssy5bu/app.bsky.feed.generator/mentions', // mentions, by flicknow + 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/bangers', // my bangers, by jaz + 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky + 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz + 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why +] -export function useGetPopularFeedsQuery() { +type GetPopularFeedsOptions = {limit?: number} + +export function createGetPopularFeedsQueryKey(...args: any[]) { + return ['getPopularFeeds', ...args] +} + +export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { + const {hasSession} = useSession() const agent = useAgent() - return useInfiniteQuery< + const limit = options?.limit || 10 + const {data: preferences} = usePreferencesQuery() + + // Make sure this doesn't invalidate unless really needed. + const selectArgs = useMemo( + () => ({ + hasSession, + savedFeeds: preferences?.savedFeeds || [], + }), + [hasSession, preferences?.savedFeeds], + ) + const lastPageCountRef = useRef(0) + + const query = useInfiniteQuery< AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema, Error, InfiniteData, QueryKey, string | undefined >({ - queryKey: useGetPopularFeedsQueryKey, + queryKey: createGetPopularFeedsQueryKey(options), queryFn: async ({pageParam}) => { const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({ - limit: 10, + limit, cursor: pageParam, }) return res.data }, initialPageParam: undefined, getNextPageParam: lastPage => lastPage.cursor, + select: useCallback( + ( + data: InfiniteData, + ) => { + const {savedFeeds, hasSession: hasSessionInner} = selectArgs + data?.pages.map(page => { + page.feeds = page.feeds.filter(feed => { + if ( + !hasSessionInner && + KNOWN_AUTHED_ONLY_FEEDS.includes(feed.uri) + ) { + return false + } + const alreadySaved = Boolean( + savedFeeds?.find(f => { + return f.value === feed.uri + }), + ) + return !alreadySaved + }) + + return page + }) + + return data + }, + [selectArgs /* Don't change. Everything needs to go into selectArgs. */], + ), }) + + useEffect(() => { + const {isFetching, hasNextPage, data} = query + if (isFetching || !hasNextPage) { + return + } + + // avoid double-fires of fetchNextPage() + if ( + lastPageCountRef.current !== 0 && + lastPageCountRef.current === data?.pages?.length + ) { + return + } + + // fetch next page if we haven't gotten a full page of content + let count = 0 + for (const page of data?.pages || []) { + count += page.feeds.length + } + if (count < limit && (data?.pages.length || 0) < 6) { + query.fetchNextPage() + lastPageCountRef.current = data?.pages?.length || 0 + } + }, [query, limit]) + + return query } export function useSearchPopularFeedsMutation() { diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index 76ff4268fd..612559455f 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -104,22 +104,6 @@ type FlatlistSlice = key: string } -// HACK -// the protocol doesn't yet tell us which feeds are personalized -// this list is used to filter out feed recommendations from logged out users -// for the ones we know need it -// -prf -const KNOWN_AUTHED_ONLY_FEEDS = [ - 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends', // popular with friends, by bsky.app - 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/mutuals', // mutuals, by skyfeed - 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/only-posts', // only posts, by skyfeed - 'at://did:plc:wzsilnxf24ehtmmc3gssy5bu/app.bsky.feed.generator/mentions', // mentions, by flicknow - 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/bangers', // my bangers, by jaz - 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky - 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz - 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why -] - export function FeedsScreen(_props: Props) { const pal = usePalette('default') const {openComposer} = useComposerControls() @@ -327,10 +311,7 @@ export function FeedsScreen(_props: Props) { type: 'popularFeedsLoading', }) } else { - if ( - !popularFeeds?.pages || - popularFeeds?.pages[0]?.feeds?.length === 0 - ) { + if (!popularFeeds?.pages) { slices.push({ key: 'popularFeedsNoResults', type: 'popularFeedsNoResults', @@ -338,26 +319,11 @@ export function FeedsScreen(_props: Props) { } else { for (const page of popularFeeds.pages || []) { slices = slices.concat( - page.feeds - .filter(feed => { - if ( - !hasSession && - KNOWN_AUTHED_ONLY_FEEDS.includes(feed.uri) - ) { - return false - } - const alreadySaved = Boolean( - preferences?.savedFeeds?.find(f => { - return f.value === feed.uri - }), - ) - return !alreadySaved - }) - .map(feed => ({ - key: `popularFeed:${feed.uri}`, - type: 'popularFeed', - feedUri: feed.uri, - })), + page.feeds.map(feed => ({ + key: `popularFeed:${feed.uri}`, + type: 'popularFeed', + feedUri: feed.uri, + })), ) } From fe3f872d493d3b80941bf496f1d5eb4c6d29fd6a Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 14 Jun 2024 11:56:43 -0500 Subject: [PATCH 006/127] Add known followers to shadow cache (#4517) --- src/state/cache/profile-shadow.ts | 2 ++ src/state/queries/known-followers.ts | 32 ++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/state/cache/profile-shadow.ts b/src/state/cache/profile-shadow.ts index 0a618ab3bb..dc907664e2 100644 --- a/src/state/cache/profile-shadow.ts +++ b/src/state/cache/profile-shadow.ts @@ -5,6 +5,7 @@ import EventEmitter from 'eventemitter3' import {batchedUpdates} from '#/lib/batchedUpdates' import {findAllProfilesInQueryData as findAllProfilesInActorSearchQueryData} from '../queries/actor-search' +import {findAllProfilesInQueryData as findAllProfilesInKnownFollowersQueryData} from '../queries/known-followers' import {findAllProfilesInQueryData as findAllProfilesInListMembersQueryData} from '../queries/list-members' import {findAllProfilesInQueryData as findAllProfilesInListConvosQueryData} from '../queries/messages/list-converations' import {findAllProfilesInQueryData as findAllProfilesInMyBlockedAccountsQueryData} from '../queries/my-blocked-accounts' @@ -111,4 +112,5 @@ function* findProfilesInCache( yield* findAllProfilesInListConvosQueryData(queryClient, did) yield* findAllProfilesInFeedsQueryData(queryClient, did) yield* findAllProfilesInPostThreadQueryData(queryClient, did) + yield* findAllProfilesInKnownFollowersQueryData(queryClient, did) } diff --git a/src/state/queries/known-followers.ts b/src/state/queries/known-followers.ts index adcbf4b502..fedd9b40f0 100644 --- a/src/state/queries/known-followers.ts +++ b/src/state/queries/known-followers.ts @@ -1,5 +1,10 @@ -import {AppBskyGraphGetKnownFollowers} from '@atproto/api' -import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query' +import {AppBskyActorDefs, AppBskyGraphGetKnownFollowers} from '@atproto/api' +import { + InfiniteData, + QueryClient, + QueryKey, + useInfiniteQuery, +} from '@tanstack/react-query' import {useAgent} from '#/state/session' @@ -32,3 +37,26 @@ export function useProfileKnownFollowersQuery(did: string | undefined) { enabled: !!did, }) } + +export function* findAllProfilesInQueryData( + queryClient: QueryClient, + did: string, +): Generator { + const queryDatas = queryClient.getQueriesData< + InfiniteData + >({ + queryKey: [RQKEY_ROOT], + }) + for (const [_queryKey, queryData] of queryDatas) { + if (!queryData?.pages) { + continue + } + for (const page of queryData?.pages) { + for (const follow of page.followers) { + if (follow.did === did) { + yield follow + } + } + } + } +} From 641a36c21dd8adf9d57353c41dfbf7ec5a48c8cd Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 14 Jun 2024 09:59:02 -0700 Subject: [PATCH 007/127] version bump (#4519) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f9a898fc9..e08aa9d29c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bsky.app", - "version": "1.86.0", + "version": "1.87.0", "private": true, "engines": { "node": ">=18" From f8c58a68a9a78689f9f636f3d739d9574a18ff7b Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 14 Jun 2024 11:59:53 -0500 Subject: [PATCH 008/127] Fix count again (#4516) --- src/components/KnownFollowers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/KnownFollowers.tsx b/src/components/KnownFollowers.tsx index a8bdb763d7..63f61ce856 100644 --- a/src/components/KnownFollowers.tsx +++ b/src/components/KnownFollowers.tsx @@ -100,7 +100,7 @@ function KnownFollowersInner({ moderation, } }) - const count = cachedKnownFollowers.count - Math.min(slice.length, 2) + const count = cachedKnownFollowers.count return ( Date: Fri, 14 Jun 2024 19:01:31 +0200 Subject: [PATCH 009/127] Resolve patch-package warnings (#4520) --- .../{expo-haptics+12.8.1.patch => expo-haptics+13.0.1.patch} | 4 ++-- ...{expo-updates+0.25.11.patch => expo-updates+0.25.14.patch} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename patches/{expo-haptics+12.8.1.patch => expo-haptics+13.0.1.patch} (96%) rename patches/{expo-updates+0.25.11.patch => expo-updates+0.25.14.patch} (96%) diff --git a/patches/expo-haptics+12.8.1.patch b/patches/expo-haptics+13.0.1.patch similarity index 96% rename from patches/expo-haptics+12.8.1.patch rename to patches/expo-haptics+13.0.1.patch index a95b56f3be..9c7b9a6663 100644 --- a/patches/expo-haptics+12.8.1.patch +++ b/patches/expo-haptics+13.0.1.patch @@ -1,9 +1,9 @@ diff --git a/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt b/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt -index 26c52af..b949a4c 100644 +index 1520465..6ea988a 100644 --- a/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt +++ b/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt @@ -42,7 +42,7 @@ class HapticsModule : Module() { - + private fun vibrate(type: HapticsVibrationType) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - vibrator.vibrate(VibrationEffect.createWaveform(type.timings, type.amplitudes, -1)) diff --git a/patches/expo-updates+0.25.11.patch b/patches/expo-updates+0.25.14.patch similarity index 96% rename from patches/expo-updates+0.25.11.patch rename to patches/expo-updates+0.25.14.patch index 5f9eceef48..6fc4fc5fcf 100644 --- a/patches/expo-updates+0.25.11.patch +++ b/patches/expo-updates+0.25.14.patch @@ -1,11 +1,11 @@ diff --git a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift -index b85291e..07a5d3c 100644 +index b85291e..546709d 100644 --- a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift +++ b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift @@ -78,13 +78,20 @@ public final class ExpoUpdatesUpdate: Update { status = UpdateStatus.StatusPending } - + + // Instead of relying on various hacks to get the correct format for the specific + // platform on the backend, we can just add this little patch.. + let dateFormatter = DateFormatter() From 36e976fe5c35f66dee51d15b1767da90e8f7a817 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 14 Jun 2024 12:32:57 -0500 Subject: [PATCH 010/127] Redo explore page (#4491) * Redo explore page, wip * Remove circle icons * Load more styling * Lower limit * Some styling tweaks * Abstract * Add tab, query, factor out * Revert unneeded change * Revert unneeded change v2 * Update copy * Load more styling * Header styles * The thin blue line * Make sure it's hairline * Update query keys * Border * Expand avis * Very load more copy --- .../arrowBottom_stroke2_corner0_rounded.svg | 1 + src/components/icons/Arrow.tsx | 4 + src/state/queries/feed.ts | 34 +- src/state/queries/suggested-follows.ts | 13 +- src/view/screens/Search/Explore.tsx | 556 ++++++++++++++++++ src/view/screens/Search/Search.tsx | 141 ++--- 6 files changed, 656 insertions(+), 93 deletions(-) create mode 100644 assets/icons/arrowBottom_stroke2_corner0_rounded.svg create mode 100644 src/view/screens/Search/Explore.tsx diff --git a/assets/icons/arrowBottom_stroke2_corner0_rounded.svg b/assets/icons/arrowBottom_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..5f4a11e09a --- /dev/null +++ b/assets/icons/arrowBottom_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/src/components/icons/Arrow.tsx b/src/components/icons/Arrow.tsx index eb753e5493..d6fb635e96 100644 --- a/src/components/icons/Arrow.tsx +++ b/src/components/icons/Arrow.tsx @@ -7,3 +7,7 @@ export const ArrowTopRight_Stroke2_Corner0_Rounded = createSinglePathSVG({ export const ArrowLeft_Stroke2_Corner0_Rounded = createSinglePathSVG({ path: 'M3 12a1 1 0 0 1 .293-.707l6-6a1 1 0 0 1 1.414 1.414L6.414 11H20a1 1 0 1 1 0 2H6.414l4.293 4.293a1 1 0 0 1-1.414 1.414l-6-6A1 1 0 0 1 3 12Z', }) + +export const ArrowBottom_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M12 21a1 1 0 0 1-.707-.293l-6-6a1 1 0 1 1 1.414-1.414L11 17.586V4a1 1 0 1 1 2 0v13.586l4.293-4.293a1 1 0 0 1 1.414 1.414l-6 6A1 1 0 0 1 12 21Z', +}) diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index fed23f5b12..2981b41b45 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -190,8 +190,10 @@ export const KNOWN_AUTHED_ONLY_FEEDS = [ type GetPopularFeedsOptions = {limit?: number} -export function createGetPopularFeedsQueryKey(...args: any[]) { - return ['getPopularFeeds', ...args] +export function createGetPopularFeedsQueryKey( + options?: GetPopularFeedsOptions, +) { + return ['getPopularFeeds', options] } export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { @@ -299,6 +301,34 @@ export function useSearchPopularFeedsMutation() { }) } +const popularFeedsSearchQueryKeyRoot = 'popularFeedsSearch' +export const createPopularFeedsSearchQueryKey = (query: string) => [ + popularFeedsSearchQueryKeyRoot, + query, +] + +export function usePopularFeedsSearch({ + query, + enabled, +}: { + query: string + enabled?: boolean +}) { + const agent = useAgent() + return useQuery({ + enabled, + queryKey: createPopularFeedsSearchQueryKey(query), + queryFn: async () => { + const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({ + limit: 10, + query: query, + }) + + return res.data.feeds + }, + }) +} + export type SavedFeedSourceInfo = FeedSourceInfo & { savedFeed: AppBskyActorDefs.SavedFeed } diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts index 59b8f7ed55..40251d43d7 100644 --- a/src/state/queries/suggested-follows.ts +++ b/src/state/queries/suggested-follows.ts @@ -23,7 +23,10 @@ import {useAgent, useSession} from '#/state/session' import {useModerationOpts} from '../preferences/moderation-opts' const suggestedFollowsQueryKeyRoot = 'suggested-follows' -const suggestedFollowsQueryKey = [suggestedFollowsQueryKeyRoot] +const suggestedFollowsQueryKey = (options?: SuggestedFollowsOptions) => [ + suggestedFollowsQueryKeyRoot, + options, +] const suggestedFollowsByActorQueryKeyRoot = 'suggested-follows-by-actor' const suggestedFollowsByActorQueryKey = (did: string) => [ @@ -31,7 +34,9 @@ const suggestedFollowsByActorQueryKey = (did: string) => [ did, ] -export function useSuggestedFollowsQuery() { +type SuggestedFollowsOptions = {limit?: number} + +export function useSuggestedFollowsQuery(options?: SuggestedFollowsOptions) { const {currentAccount} = useSession() const agent = useAgent() const moderationOpts = useModerationOpts() @@ -46,12 +51,12 @@ export function useSuggestedFollowsQuery() { >({ enabled: !!moderationOpts && !!preferences, staleTime: STALE.HOURS.ONE, - queryKey: suggestedFollowsQueryKey, + queryKey: suggestedFollowsQueryKey(options), queryFn: async ({pageParam}) => { const contentLangs = getContentLanguages().join(',') const res = await agent.app.bsky.actor.getSuggestions( { - limit: 25, + limit: options?.limit || 25, cursor: pageParam, }, { diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx new file mode 100644 index 0000000000..f6e998838a --- /dev/null +++ b/src/view/screens/Search/Explore.tsx @@ -0,0 +1,556 @@ +import React from 'react' +import {View} from 'react-native' +import { + AppBskyActorDefs, + AppBskyFeedDefs, + moderateProfile, + ModerationDecision, + ModerationOpts, +} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {logger} from '#/logger' +import {isWeb} from '#/platform/detection' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {useGetPopularFeedsQuery} from '#/state/queries/feed' +import {usePreferencesQuery} from '#/state/queries/preferences' +import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows' +import {useSession} from '#/state/session' +import {cleanError} from 'lib/strings/errors' +import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' +import {List} from '#/view/com/util/List' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' +import { + FeedFeedLoadingPlaceholder, + ProfileCardFeedLoadingPlaceholder, +} from 'view/com/util/LoadingPlaceholder' +import {atoms as a, useTheme, ViewStyleProp} from '#/alf' +import {Button} from '#/components/Button' +import {ArrowBottom_Stroke2_Corner0_Rounded as ArrowBottom} from '#/components/icons/Arrow' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import {Props as SVGIconProps} from '#/components/icons/common' +import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle' +import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle' +import {Loader} from '#/components/Loader' +import {Text} from '#/components/Typography' + +function SuggestedItemsHeader({ + title, + description, + style, + icon: Icon, +}: { + title: string + description: string + icon: React.ComponentType +} & ViewStyleProp) { + const t = useTheme() + + return ( + + + + + {title} + + + {description} + + + + ) +} + +type LoadMoreItems = + | { + type: 'profile' + key: string + avatar: string + moderation: ModerationDecision + } + | { + type: 'feed' + key: string + avatar: string + moderation: undefined + } + +function LoadMore({ + item, + moderationOpts, +}: { + item: ExploreScreenItems & {type: 'loadMore'} + moderationOpts?: ModerationOpts +}) { + const t = useTheme() + const {_} = useLingui() + const items = React.useMemo(() => { + return item.items + .map(_item => { + if (_item.type === 'profile') { + return { + type: 'profile', + key: _item.profile.did, + avatar: _item.profile.avatar, + moderation: moderateProfile(_item.profile, moderationOpts!), + } + } else if (_item.type === 'feed') { + return { + type: 'feed', + key: _item.feed.uri, + avatar: _item.feed.avatar, + moderation: undefined, + } + } + return undefined + }) + .filter(Boolean) as LoadMoreItems[] + }, [item.items, moderationOpts]) + const type = items[0].type + + return ( + + + + ) +} + +type ExploreScreenItems = + | { + type: 'header' + key: string + title: string + description: string + style?: ViewStyleProp['style'] + icon: React.ComponentType + } + | { + type: 'profile' + key: string + profile: AppBskyActorDefs.ProfileViewBasic + } + | { + type: 'feed' + key: string + feed: AppBskyFeedDefs.GeneratorView + } + | { + type: 'loadMore' + key: string + isLoadingMore: boolean + onLoadMore: () => void + items: ExploreScreenItems[] + } + | { + type: 'profilePlaceholder' + key: string + } + | { + type: 'feedPlaceholder' + key: string + } + | { + type: 'error' + key: string + message: string + error: string + } + +export function Explore() { + const {_} = useLingui() + const t = useTheme() + const {hasSession} = useSession() + const {data: preferences, error: preferencesError} = usePreferencesQuery() + const moderationOpts = useModerationOpts() + const { + data: profiles, + hasNextPage: hasNextProfilesPage, + isLoading: isLoadingProfiles, + isFetchingNextPage: isFetchingNextProfilesPage, + error: profilesError, + fetchNextPage: fetchNextProfilesPage, + } = useSuggestedFollowsQuery({limit: 3}) + const { + data: feeds, + hasNextPage: hasNextFeedsPage, + isLoading: isLoadingFeeds, + isFetchingNextPage: isFetchingNextFeedsPage, + error: feedsError, + fetchNextPage: fetchNextFeedsPage, + } = useGetPopularFeedsQuery({limit: 3}) + + const isLoadingMoreProfiles = isFetchingNextProfilesPage && !isLoadingProfiles + const onLoadMoreProfiles = React.useCallback(async () => { + if (isFetchingNextProfilesPage || !hasNextProfilesPage || profilesError) + return + try { + await fetchNextProfilesPage() + } catch (err) { + logger.error('Failed to load more suggested follows', {message: err}) + } + }, [ + isFetchingNextProfilesPage, + hasNextProfilesPage, + profilesError, + fetchNextProfilesPage, + ]) + + const isLoadingMoreFeeds = isFetchingNextFeedsPage && !isLoadingFeeds + const onLoadMoreFeeds = React.useCallback(async () => { + if (isFetchingNextFeedsPage || !hasNextFeedsPage || feedsError) return + try { + await fetchNextFeedsPage() + } catch (err) { + logger.error('Failed to load more suggested follows', {message: err}) + } + }, [ + isFetchingNextFeedsPage, + hasNextFeedsPage, + feedsError, + fetchNextFeedsPage, + ]) + + const items = React.useMemo(() => { + const i: ExploreScreenItems[] = [ + { + type: 'header', + key: 'suggested-follows-header', + title: _(msg`Suggested accounts`), + description: _( + msg`Follow more accounts to get connected to your interests and build your network.`, + ), + icon: Person, + }, + ] + + if (profiles) { + // Currently the responses contain duplicate items. + // Needs to be fixed on backend, but let's dedupe to be safe. + let seen = new Set() + for (const page of profiles.pages) { + for (const actor of page.actors) { + if (!seen.has(actor.did)) { + seen.add(actor.did) + i.push({ + type: 'profile', + key: actor.did, + profile: actor, + }) + } + } + } + + i.push({ + type: 'loadMore', + key: 'loadMoreProfiles', + isLoadingMore: isLoadingMoreProfiles, + onLoadMore: onLoadMoreProfiles, + items: i.filter(item => item.type === 'profile').slice(-3), + }) + } else { + if (profilesError) { + i.push({ + type: 'error', + key: 'profilesError', + message: _(msg`Failed to load suggested follows`), + error: cleanError(profilesError), + }) + } else { + i.push({type: 'profilePlaceholder', key: 'profilePlaceholder'}) + } + } + + i.push({ + type: 'header', + key: 'suggested-feeds-header', + title: _(msg`Discover new feeds`), + description: _( + msg`Custom feeds built by the community bring you new experiences and help you find the content you love.`, + ), + style: [a.pt_5xl], + icon: ListSparkle, + }) + + if (feeds && preferences) { + // Currently the responses contain duplicate items. + // Needs to be fixed on backend, but let's dedupe to be safe. + let seen = new Set() + for (const page of feeds.pages) { + for (const feed of page.feeds) { + if (!seen.has(feed.uri)) { + seen.add(feed.uri) + i.push({ + type: 'feed', + key: feed.uri, + feed, + }) + } + } + } + + if (feedsError) { + i.push({ + type: 'error', + key: 'feedsError', + message: _(msg`Failed to load suggested feeds`), + error: cleanError(feedsError), + }) + } else if (preferencesError) { + i.push({ + type: 'error', + key: 'preferencesError', + message: _(msg`Failed to load feeds preferences`), + error: cleanError(preferencesError), + }) + } else { + i.push({ + type: 'loadMore', + key: 'loadMoreFeeds', + isLoadingMore: isLoadingMoreFeeds, + onLoadMore: onLoadMoreFeeds, + items: i.filter(item => item.type === 'feed').slice(-3), + }) + } + } else { + if (feedsError) { + i.push({ + type: 'error', + key: 'feedsError', + message: _(msg`Failed to load suggested feeds`), + error: cleanError(feedsError), + }) + } else if (preferencesError) { + i.push({ + type: 'error', + key: 'preferencesError', + message: _(msg`Failed to load feeds preferences`), + error: cleanError(preferencesError), + }) + } else { + i.push({type: 'feedPlaceholder', key: 'feedPlaceholder'}) + } + } + + return i + }, [ + _, + profiles, + feeds, + preferences, + onLoadMoreFeeds, + onLoadMoreProfiles, + isLoadingMoreProfiles, + isLoadingMoreFeeds, + profilesError, + feedsError, + preferencesError, + ]) + + const renderItem = React.useCallback( + ({item}: {item: ExploreScreenItems}) => { + switch (item.type) { + case 'header': { + return ( + + ) + } + case 'profile': { + return ( + + + + ) + } + case 'feed': { + return ( + + + + ) + } + case 'loadMore': { + return + } + case 'profilePlaceholder': { + return + } + case 'feedPlaceholder': { + return + } + case 'error': { + return ( + + + + + + {item.message} + + + {item.error} + + + + + ) + } + } + }, + [t, hasSession, moderationOpts], + ) + + return ( + item.key} + // @ts-ignore web only -prf + desktopFixedHeight + contentContainerStyle={{paddingBottom: 200}} + keyboardShouldPersistTaps="handled" + keyboardDismissMode="on-drag" + /> + ) +} diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index b6daf84b38..f1b0301d00 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -29,15 +29,14 @@ import {MagnifyingGlassIcon} from '#/lib/icons' import {makeProfileLink} from '#/lib/routes/links' import {NavigationProp} from '#/lib/routes/types' import {augmentSearchQuery} from '#/lib/strings/helpers' -import {s} from '#/lib/styles' import {logger} from '#/logger' import {isIOS, isNative, isWeb} from '#/platform/detection' import {listenSoftReset} from '#/state/events' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {useActorSearch} from '#/state/queries/actor-search' +import {usePopularFeedsSearch} from '#/state/queries/feed' import {useSearchPostsQuery} from '#/state/queries/search-posts' -import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows' import {useSession} from '#/state/session' import {useSetDrawerOpen} from '#/state/shell' import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell' @@ -56,8 +55,9 @@ import {Link} from '#/view/com/util/Link' import {List} from '#/view/com/util/List' import {Text} from '#/view/com/util/text/Text' import {CenteredView, ScrollView} from '#/view/com/util/Views' +import {Explore} from '#/view/screens/Search/Explore' import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search' -import {ProfileCardFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' +import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' import {atoms as a} from '#/alf' import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' @@ -122,70 +122,6 @@ function EmptyState({message, error}: {message: string; error?: string}) { ) } -function useSuggestedFollows(): [ - AppBskyActorDefs.ProfileViewBasic[], - () => void, -] { - const { - data: suggestions, - hasNextPage, - isFetchingNextPage, - isError, - fetchNextPage, - } = useSuggestedFollowsQuery() - - const onEndReached = React.useCallback(async () => { - if (isFetchingNextPage || !hasNextPage || isError) return - try { - await fetchNextPage() - } catch (err) { - logger.error('Failed to load more suggested follows', {message: err}) - } - }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) - - const items: AppBskyActorDefs.ProfileViewBasic[] = [] - if (suggestions) { - // Currently the responses contain duplicate items. - // Needs to be fixed on backend, but let's dedupe to be safe. - let seen = new Set() - for (const page of suggestions.pages) { - for (const actor of page.actors) { - if (!seen.has(actor.did)) { - seen.add(actor.did) - items.push(actor) - } - } - } - } - return [items, onEndReached] -} - -let SearchScreenSuggestedFollows = (_props: {}): React.ReactNode => { - const pal = usePalette('default') - const [suggestions, onEndReached] = useSuggestedFollows() - - return suggestions.length ? ( - } - keyExtractor={item => item.did} - // @ts-ignore web only -prf - desktopFixedHeight - contentContainerStyle={{paddingBottom: 200}} - keyboardShouldPersistTaps="handled" - keyboardDismissMode="on-drag" - onEndReached={onEndReached} - onEndReachedThreshold={2} - /> - ) : ( - - - - - ) -} -SearchScreenSuggestedFollows = React.memo(SearchScreenSuggestedFollows) - type SearchResultSlice = | { type: 'post' @@ -342,6 +278,50 @@ let SearchScreenUserResults = ({ } SearchScreenUserResults = React.memo(SearchScreenUserResults) +let SearchScreenFeedsResults = ({ + query, + active, +}: { + query: string + active: boolean +}): React.ReactNode => { + const {_} = useLingui() + const {hasSession} = useSession() + + const {data: results, isFetched} = usePopularFeedsSearch({ + query, + enabled: active, + }) + + return isFetched && results ? ( + <> + {results.length ? ( + ( + + )} + keyExtractor={item => item.did} + // @ts-ignore web only -prf + desktopFixedHeight + contentContainerStyle={{paddingBottom: 100}} + /> + ) : ( + + )} + + ) : ( + + ) +} +SearchScreenFeedsResults = React.memo(SearchScreenFeedsResults) + let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => { const pal = usePalette('default') const setMinimalShellMode = useSetMinimalShellMode() @@ -389,6 +369,12 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => { ), }, + { + title: _(msg`Feeds`), + component: ( + + ), + }, ] }, [_, query, activeTab]) @@ -408,26 +394,7 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => { ))} ) : hasSession ? ( - - - - Suggested Follows - - - - - + ) : ( Date: Sat, 15 Jun 2024 02:39:08 +0900 Subject: [PATCH 011/127] Fix kawaii logo (#4505) --- src/view/com/home/HomeHeaderLayout.web.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/view/com/home/HomeHeaderLayout.web.tsx b/src/view/com/home/HomeHeaderLayout.web.tsx index 77bdba51fd..28f29ec787 100644 --- a/src/view/com/home/HomeHeaderLayout.web.tsx +++ b/src/view/com/home/HomeHeaderLayout.web.tsx @@ -57,6 +57,7 @@ function HomeHeaderLayoutDesktopAndTablet({ t.atoms.bg, t.atoms.border_contrast_low, styles.bar, + kawaii && {paddingTop: 22, paddingBottom: 16}, ]}> From 5751014117ff87a0b5188841b468095f00305ed5 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 14 Jun 2024 14:24:04 -0500 Subject: [PATCH 012/127] Feed source card (#4512) * Pass event through click handlers * Add FeedCard, use in Feeds screen * Tweak space * Don't contrain rt height * Tweak space * Fix type errors, don't pass event to fns that don't expect it * Show unresolved RT prior to facet resolution --- src/components/FeedCard.tsx | 198 ++++++++++++++++++ src/components/Prompt.tsx | 17 +- src/components/dms/LeaveConvoPrompt.tsx | 2 +- .../Profile/Header/ProfileHeaderLabeler.tsx | 2 +- src/view/com/util/post-embeds/GifEmbed.tsx | 2 +- src/view/screens/Feeds.tsx | 22 +- 6 files changed, 222 insertions(+), 21 deletions(-) create mode 100644 src/components/FeedCard.tsx diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx new file mode 100644 index 0000000000..2745ed7c9f --- /dev/null +++ b/src/components/FeedCard.tsx @@ -0,0 +1,198 @@ +import React from 'react' +import {GestureResponderEvent, View} from 'react-native' +import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api' +import {msg, plural, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {logger} from '#/logger' +import { + useAddSavedFeedsMutation, + usePreferencesQuery, + useRemoveFeedMutation, +} from '#/state/queries/preferences' +import {sanitizeHandle} from 'lib/strings/handles' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import * as Toast from 'view/com/util/Toast' +import {useTheme} from '#/alf' +import {atoms as a} from '#/alf' +import {Button, ButtonIcon} from '#/components/Button' +import {useRichText} from '#/components/hooks/useRichText' +import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' +import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' +import {Link as InternalLink} from '#/components/Link' +import {Loader} from '#/components/Loader' +import * as Prompt from '#/components/Prompt' +import {RichText} from '#/components/RichText' +import {Text} from '#/components/Typography' + +export function Default({feed}: {feed: AppBskyFeedDefs.GeneratorView}) { + return ( + + +
+ + + +
+ + +
+ + ) +} + +export function Link({ + children, + feed, +}: { + children: React.ReactElement + feed: AppBskyFeedDefs.GeneratorView +}) { + const href = React.useMemo(() => { + const urip = new AtUri(feed.uri) + const handleOrDid = feed.creator.handle || feed.creator.did + return `/profile/${handleOrDid}/feed/${urip.rkey}` + }, [feed]) + return {children} +} + +export function Outer({children}: {children: React.ReactNode}) { + return {children} +} + +export function Header({children}: {children: React.ReactNode}) { + return {children} +} + +export function Avatar({src}: {src: string | undefined}) { + return +} + +export function TitleAndByline({ + title, + creator, +}: { + title: string + creator: AppBskyActorDefs.ProfileViewBasic +}) { + const t = useTheme() + + return ( + + + {title} + + + Feed by {sanitizeHandle(creator.handle, '@')} + + + ) +} + +export function Description({description}: {description?: string}) { + const [rt, isResolving] = useRichText(description || '') + if (!description) return null + return isResolving ? ( + + ) : ( + + ) +} + +export function Likes({count}: {count: number}) { + const t = useTheme() + return ( + + {plural(count || 0, { + one: 'Liked by # user', + other: 'Liked by # users', + })} + + ) +} + +export function Action({uri, pin}: {uri: string; pin?: boolean}) { + const {_} = useLingui() + const {data: preferences} = usePreferencesQuery() + const {isPending: isAddSavedFeedPending, mutateAsync: saveFeeds} = + useAddSavedFeedsMutation() + const {isPending: isRemovePending, mutateAsync: removeFeed} = + useRemoveFeedMutation() + const savedFeedConfig = React.useMemo(() => { + return preferences?.savedFeeds?.find( + feed => feed.type === 'feed' && feed.value === uri, + ) + }, [preferences?.savedFeeds, uri]) + const removePromptControl = Prompt.usePromptControl() + const isPending = isAddSavedFeedPending || isRemovePending + + const toggleSave = React.useCallback( + async (e: GestureResponderEvent) => { + e.preventDefault() + e.stopPropagation() + + try { + if (savedFeedConfig) { + await removeFeed(savedFeedConfig) + } else { + await saveFeeds([ + { + type: 'feed', + value: uri, + pinned: pin || false, + }, + ]) + } + Toast.show(_(msg`Feeds updated!`)) + } catch (e: any) { + logger.error(e, {context: `FeedCard: failed to update feeds`, pin}) + Toast.show(_(msg`Failed to update feeds`)) + } + }, + [_, pin, saveFeeds, removeFeed, uri, savedFeedConfig], + ) + + const onPrompRemoveFeed = React.useCallback( + async (e: GestureResponderEvent) => { + e.preventDefault() + e.stopPropagation() + + removePromptControl.open() + }, + [removePromptControl], + ) + + return ( + <> + + + + + ) +} diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx index d05cab5ab6..315ad0dfda 100644 --- a/src/components/Prompt.tsx +++ b/src/components/Prompt.tsx @@ -1,10 +1,10 @@ import React from 'react' -import {View} from 'react-native' +import {GestureResponderEvent, View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {atoms as a, useBreakpoints, useTheme} from '#/alf' -import {Button, ButtonColor, ButtonText} from '#/components/Button' +import {Button, ButtonColor, ButtonProps, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {Text} from '#/components/Typography' @@ -136,7 +136,7 @@ export function Action({ * Note: The dialog will close automatically when the action is pressed, you * should NOT close the dialog as a side effect of this method. */ - onPress: () => void + onPress: ButtonProps['onPress'] color?: ButtonColor /** * Optional i18n string. If undefined, it will default to "Confirm". @@ -147,9 +147,12 @@ export function Action({ const {_} = useLingui() const {gtMobile} = useBreakpoints() const {close} = Dialog.useDialogContext() - const handleOnPress = React.useCallback(() => { - close(onPress) - }, [close, onPress]) + const handleOnPress = React.useCallback( + (e: GestureResponderEvent) => { + close(() => onPress?.(e)) + }, + [close, onPress], + ) return ( + + + + + + + Say hello! + + + + {profileName} joined Bluesky{' '} + {timeAgo(createdAt, {format: 'long'})} ago + + + + + +
+ ) +} diff --git a/src/components/icons/Newskie.tsx b/src/components/icons/Newskie.tsx new file mode 100644 index 0000000000..ddbb33201e --- /dev/null +++ b/src/components/icons/Newskie.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Newskie = createSinglePathSVG({ + path: 'M11.183 8.561c0 .544.348.984.892.984.545 0 .893-.44.893-.985V6.985c0-.544-.348-.985-.893-.985-.543 0-.892.44-.892.985v1.576Zm5.94 7.481c0 .539-.438.942-.976.942H8.004c-.538 0-.975-.411-.975-.95 0-2.782 2.264-5.021 5.046-5.021 2.783 0 5.047 2.247 5.047 5.03Zm-.43-4.584a.983.983 0 0 1 0-1.393l1.114-1.114a.985.985 0 0 1 1.393 1.393l-1.114 1.114a.985.985 0 0 1-1.393 0Zm2.897 3.741h1.575c.544 0 .985.349.985.892 0 .544-.44.892-.985.892h-1.67a.872.872 0 0 1-.89-.887c0-.543.44-.897.985-.897Zm-14.045.893c0-.544-.44-.892-.985-.892H2.985c-.544 0-.985.349-.985.892 0 .544.44.892.985.892H4.56c.545 0 .985-.349.985-.892Zm1.913-6.027a.985.985 0 0 1-1.393 1.393L4.95 10.344A.985.985 0 0 1 6.344 8.95l1.114 1.114Z', +}) diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx index 9ab24fbbed..4f438a2868 100644 --- a/src/screens/Profile/Header/Handle.tsx +++ b/src/screens/Profile/Header/Handle.tsx @@ -5,7 +5,9 @@ import {Trans} from '@lingui/macro' import {Shadow} from '#/state/cache/types' import {isInvalidHandle} from 'lib/strings/handles' +import {isAndroid} from 'platform/detection' import {atoms as a, useTheme, web} from '#/alf' +import {NewskieDialog} from '#/components/NewskieDialog' import {Text} from '#/components/Typography' export function ProfileHeaderHandle({ @@ -17,7 +19,10 @@ export function ProfileHeaderHandle({ const invalidHandle = isInvalidHandle(profile.handle) const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy return ( - + + {profile.viewer?.followedBy && !blockHide ? ( From 35e54e24a0b08ce0f2e3389aeb4fb0f29778170e Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 18 Jun 2024 13:37:14 -0500 Subject: [PATCH 039/127] Explore fixes (#4540) * Use safe check, check for next page, handle varied lengths * Fix border width * Move safe check * Add font_heavy and use it on the explore page headers --------- Co-authored-by: Paul Frazee --- src/alf/atoms.ts | 3 +++ src/alf/tokens.ts | 1 + src/view/screens/Search/Explore.tsx | 31 ++++++++++++++++++----------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index 1ccb0460c4..1dc2dfa7b0 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -267,6 +267,9 @@ export const atoms = { font_bold: { fontWeight: tokens.fontWeight.bold, }, + font_heavy: { + fontWeight: tokens.fontWeight.heavy, + }, italic: { fontStyle: 'italic', }, diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts index 1bddd95d43..675844e296 100644 --- a/src/alf/tokens.ts +++ b/src/alf/tokens.ts @@ -118,6 +118,7 @@ export const fontWeight = { normal: '400', semibold: '500', bold: '600', + heavy: '700', } as const export const gradients = { diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index c7f5f939fe..dd93bf8130 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -64,7 +64,7 @@ function SuggestedItemsHeader({ fill={t.palette.primary_500} style={{marginLeft: -2}} /> - {title} + {title} {description} @@ -119,6 +119,9 @@ function LoadMore({ }) .filter(Boolean) as LoadMoreItems[] }, [item.items, moderationOpts]) + + if (items.length === 0) return null + const type = items[0].type return ( @@ -142,20 +145,20 @@ function LoadMore({ a.relative, { height: 32, - width: 32 + 15 * 3, + width: 32 + 15 * items.length, }, ]}> item.type === 'profile').slice(-3), - }) + if (hasNextProfilesPage) { + i.push({ + type: 'loadMore', + key: 'loadMoreProfiles', + isLoadingMore: isLoadingMoreProfiles, + onLoadMore: onLoadMoreProfiles, + items: i.filter(item => item.type === 'profile').slice(-3), + }) + } } else { if (profilesError) { i.push({ @@ -412,7 +417,7 @@ export function Explore() { message: _(msg`Failed to load feeds preferences`), error: cleanError(preferencesError), }) - } else { + } else if (hasNextFeedsPage) { i.push({ type: 'loadMore', key: 'loadMoreFeeds', @@ -454,6 +459,8 @@ export function Explore() { profilesError, feedsError, preferencesError, + hasNextProfilesPage, + hasNextFeedsPage, ]) const renderItem = React.useCallback( From 5f5d845053e13169f89fc70a3f858b0a9e5ed4fd Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 18 Jun 2024 19:48:34 +0100 Subject: [PATCH 040/127] Server-side thread mutes (#4518) * update atproto/api * move thread mutes to server side * rm log * move muted threads provider to inside did key * use map instead of object --- package.json | 2 +- src/App.native.tsx | 76 ++++++++++----------- src/App.web.tsx | 72 +++++++++---------- src/lib/statsig/events.ts | 2 + src/state/cache/thread-mutes.tsx | 44 ++++++++++++ src/state/muted-threads.tsx | 62 ----------------- src/state/queries/notifications/feed.ts | 3 - src/state/queries/notifications/unread.tsx | 5 +- src/state/queries/notifications/util.ts | 50 -------------- src/state/queries/post.ts | 70 +++++++++++++++++++ src/view/com/util/forms/PostDropdownBtn.tsx | 45 ++++++------ src/view/com/util/post-ctrls/PostCtrls.tsx | 4 +- yarn.lock | 8 +-- 13 files changed, 223 insertions(+), 220 deletions(-) create mode 100644 src/state/cache/thread-mutes.tsx delete mode 100644 src/state/muted-threads.tsx diff --git a/package.json b/package.json index 29e198c9cc..4178369035 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web" }, "dependencies": { - "@atproto/api": "^0.12.18", + "@atproto/api": "^0.12.19", "@bam.tech/react-native-image-resizer": "^3.0.4", "@braintree/sanitize-url": "^6.0.2", "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", diff --git a/src/App.native.tsx b/src/App.native.tsx index 322e944a47..18461fdd05 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -14,40 +14,40 @@ import * as SplashScreen from 'expo-splash-screen' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useIntentHandler} from '#/lib/hooks/useIntentHandler' +import {QueryProvider} from '#/lib/react-query' import { initialize, Provider as StatsigProvider, tryFetchGates, } from '#/lib/statsig/statsig' +import {s} from '#/lib/styles' +import {ThemeProvider} from '#/lib/ThemeContext' import {logger} from '#/logger' +import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' +import {Provider as DialogStateProvider} from '#/state/dialogs' +import {Provider as InvitesStateProvider} from '#/state/invites' +import {Provider as LightboxStateProvider} from '#/state/lightbox' import {MessagesProvider} from '#/state/messages' +import {Provider as ModalStateProvider} from '#/state/modals' import {init as initPersistedState} from '#/state/persisted' +import {Provider as PrefsStateProvider} from '#/state/preferences' import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs' import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation-opts' -import {readLastActiveAccount} from '#/state/session/util' -import {useIntentHandler} from 'lib/hooks/useIntentHandler' -import {QueryProvider} from 'lib/react-query' -import {s} from 'lib/styles' -import {ThemeProvider} from 'lib/ThemeContext' -import {Provider as DialogStateProvider} from 'state/dialogs' -import {Provider as InvitesStateProvider} from 'state/invites' -import {Provider as LightboxStateProvider} from 'state/lightbox' -import {Provider as ModalStateProvider} from 'state/modals' -import {Provider as MutedThreadsProvider} from 'state/muted-threads' -import {Provider as PrefsStateProvider} from 'state/preferences' -import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' +import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread' import { Provider as SessionProvider, SessionAccount, useSession, useSessionApi, -} from 'state/session' -import {Provider as ShellStateProvider} from 'state/shell' -import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out' -import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed' -import {TestCtrls} from 'view/com/testing/TestCtrls' -import * as Toast from 'view/com/util/Toast' -import {Shell} from 'view/shell' +} from '#/state/session' +import {readLastActiveAccount} from '#/state/session/util' +import {Provider as ShellStateProvider} from '#/state/shell' +import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' +import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' +import {TestCtrls} from '#/view/com/testing/TestCtrls' +import * as Toast from '#/view/com/util/Toast' +import {Shell} from '#/view/shell' import {ThemeProvider as Alf} from '#/alf' import {useColorModeTheme} from '#/alf/util/useColorModeTheme' import {Provider as PortalProvider} from '#/components/Portal' @@ -112,10 +112,12 @@ function InnerApp() { - - - - + + + + + + @@ -154,21 +156,19 @@ function App() { - - - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/src/App.web.tsx b/src/App.web.tsx index 5c4dc4e637..6af3c7d6fb 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -8,35 +8,35 @@ import {SafeAreaProvider} from 'react-native-safe-area-context' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useIntentHandler} from '#/lib/hooks/useIntentHandler' +import {QueryProvider} from '#/lib/react-query' import {Provider as StatsigProvider} from '#/lib/statsig/statsig' +import {ThemeProvider} from '#/lib/ThemeContext' import {logger} from '#/logger' +import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' +import {Provider as DialogStateProvider} from '#/state/dialogs' +import {Provider as InvitesStateProvider} from '#/state/invites' +import {Provider as LightboxStateProvider} from '#/state/lightbox' import {MessagesProvider} from '#/state/messages' +import {Provider as ModalStateProvider} from '#/state/modals' import {init as initPersistedState} from '#/state/persisted' +import {Provider as PrefsStateProvider} from '#/state/preferences' import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs' import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation-opts' -import {readLastActiveAccount} from '#/state/session/util' -import {useIntentHandler} from 'lib/hooks/useIntentHandler' -import {QueryProvider} from 'lib/react-query' -import {ThemeProvider} from 'lib/ThemeContext' -import {Provider as DialogStateProvider} from 'state/dialogs' -import {Provider as InvitesStateProvider} from 'state/invites' -import {Provider as LightboxStateProvider} from 'state/lightbox' -import {Provider as ModalStateProvider} from 'state/modals' -import {Provider as MutedThreadsProvider} from 'state/muted-threads' -import {Provider as PrefsStateProvider} from 'state/preferences' -import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' +import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread' import { Provider as SessionProvider, SessionAccount, useSession, useSessionApi, -} from 'state/session' -import {Provider as ShellStateProvider} from 'state/shell' -import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out' -import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed' -import * as Toast from 'view/com/util/Toast' -import {ToastContainer} from 'view/com/util/Toast.web' -import {Shell} from 'view/shell/index' +} from '#/state/session' +import {readLastActiveAccount} from '#/state/session/util' +import {Provider as ShellStateProvider} from '#/state/shell' +import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' +import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' +import * as Toast from '#/view/com/util/Toast' +import {ToastContainer} from '#/view/com/util/Toast.web' +import {Shell} from '#/view/shell/index' import {ThemeProvider as Alf} from '#/alf' import {useColorModeTheme} from '#/alf/util/useColorModeTheme' import {Provider as PortalProvider} from '#/components/Portal' @@ -96,9 +96,11 @@ function InnerApp() { - - - + + + + + @@ -136,21 +138,19 @@ function App() { - - - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index 9939f60c9c..0d77ec8a36 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -103,6 +103,8 @@ export type LogEvents = { 'post:unrepost': { logContext: 'FeedItem' | 'PostThreadItem' | 'Post' } + 'post:mute': {} + 'post:unmute': {} 'profile:follow': { didBecomeMutual: boolean | undefined followeeClout: number | undefined diff --git a/src/state/cache/thread-mutes.tsx b/src/state/cache/thread-mutes.tsx new file mode 100644 index 0000000000..b58bd430f5 --- /dev/null +++ b/src/state/cache/thread-mutes.tsx @@ -0,0 +1,44 @@ +import React from 'react' + +type StateContext = Map +type SetStateContext = (uri: string, value: boolean) => void + +const stateContext = React.createContext(new Map()) +const setStateContext = React.createContext( + (_: string) => false, +) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [state, setState] = React.useState(() => new Map()) + + const setThreadMute = React.useCallback( + (uri: string, value: boolean) => { + setState(prev => { + const next = new Map(prev) + next.set(uri, value) + return next + }) + }, + [setState], + ) + return ( + + + {children} + + + ) +} + +export function useMutedThreads() { + return React.useContext(stateContext) +} + +export function useIsThreadMuted(uri: string, defaultValue = false) { + const state = React.useContext(stateContext) + return state.get(uri) ?? defaultValue +} + +export function useSetThreadMute() { + return React.useContext(setStateContext) +} diff --git a/src/state/muted-threads.tsx b/src/state/muted-threads.tsx deleted file mode 100644 index 84a717eb79..0000000000 --- a/src/state/muted-threads.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react' -import * as persisted from '#/state/persisted' -import {track} from '#/lib/analytics/analytics' - -type StateContext = persisted.Schema['mutedThreads'] -type ToggleContext = (uri: string) => boolean - -const stateContext = React.createContext( - persisted.defaults.mutedThreads, -) -const toggleContext = React.createContext((_: string) => false) - -export function Provider({children}: React.PropsWithChildren<{}>) { - const [state, setState] = React.useState(persisted.get('mutedThreads')) - - const toggleThreadMute = React.useCallback( - (uri: string) => { - let muted = false - setState((arr: string[]) => { - if (arr.includes(uri)) { - arr = arr.filter(v => v !== uri) - muted = false - track('Post:ThreadUnmute') - } else { - arr = arr.concat([uri]) - muted = true - track('Post:ThreadMute') - } - persisted.write('mutedThreads', arr) - return arr - }) - return muted - }, - [setState], - ) - - React.useEffect(() => { - return persisted.onUpdate(() => { - setState(persisted.get('mutedThreads')) - }) - }, [setState]) - - return ( - - - {children} - - - ) -} - -export function useMutedThreads() { - return React.useContext(stateContext) -} - -export function useToggleThreadMute() { - return React.useContext(toggleContext) -} - -export function isThreadMuted(uri: string) { - return persisted.get('mutedThreads').includes(uri) -} diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts index d9f019af38..0607f07a10 100644 --- a/src/state/queries/notifications/feed.ts +++ b/src/state/queries/notifications/feed.ts @@ -26,7 +26,6 @@ import { useQueryClient, } from '@tanstack/react-query' -import {useMutedThreads} from '#/state/muted-threads' import {useAgent} from '#/state/session' import {useModerationOpts} from '../../preferences/moderation-opts' import {STALE} from '..' @@ -54,7 +53,6 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { const agent = useAgent() const queryClient = useQueryClient() const moderationOpts = useModerationOpts() - const threadMutes = useMutedThreads() const unreads = useUnreadNotificationsApi() const enabled = opts?.enabled !== false const lastPageCountRef = useRef(0) @@ -82,7 +80,6 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { cursor: pageParam, queryClient, moderationOpts, - threadMutes, fetchAdditionalData: true, }) ).page diff --git a/src/state/queries/notifications/unread.tsx b/src/state/queries/notifications/unread.tsx index ffb8d03bc7..7bb325ea98 100644 --- a/src/state/queries/notifications/unread.tsx +++ b/src/state/queries/notifications/unread.tsx @@ -9,7 +9,6 @@ import EventEmitter from 'eventemitter3' import BroadcastChannel from '#/lib/broadcast' import {logger} from '#/logger' -import {useMutedThreads} from '#/state/muted-threads' import {useAgent, useSession} from '#/state/session' import {resetBadgeCount} from 'lib/notifications/notifications' import {useModerationOpts} from '../../preferences/moderation-opts' @@ -48,7 +47,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const agent = useAgent() const queryClient = useQueryClient() const moderationOpts = useModerationOpts() - const threadMutes = useMutedThreads() const [numUnread, setNumUnread] = React.useState('') @@ -147,7 +145,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) { limit: 40, queryClient, moderationOpts, - threadMutes, // only fetch subjects when the page is going to be used // in the notifications query, otherwise skip it @@ -192,7 +189,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { } }, } - }, [setNumUnread, queryClient, moderationOpts, threadMutes, agent]) + }, [setNumUnread, queryClient, moderationOpts, agent]) checkUnreadRef.current = api.checkUnread return ( diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts index 4662493533..8ed1c0390c 100644 --- a/src/state/queries/notifications/util.ts +++ b/src/state/queries/notifications/util.ts @@ -1,5 +1,4 @@ import { - AppBskyEmbedRecord, AppBskyFeedDefs, AppBskyFeedLike, AppBskyFeedPost, @@ -28,7 +27,6 @@ export async function fetchPage({ limit, queryClient, moderationOpts, - threadMutes, fetchAdditionalData, }: { agent: BskyAgent @@ -36,7 +34,6 @@ export async function fetchPage({ limit: number queryClient: QueryClient moderationOpts: ModerationOpts | undefined - threadMutes: string[] fetchAdditionalData: boolean }): Promise<{page: FeedPage; indexedAt: string | undefined}> { const res = await agent.listNotifications({ @@ -67,11 +64,6 @@ export async function fetchPage({ } } - // apply thread muting - notifsGrouped = notifsGrouped.filter( - notif => !isThreadMuted(notif, threadMutes), - ) - let seenAt = res.data.seenAt ? new Date(res.data.seenAt) : new Date() if (Number.isNaN(seenAt.getTime())) { seenAt = new Date() @@ -207,45 +199,3 @@ function getSubjectUri( return notif.reasonSubject } } - -export function isThreadMuted(notif: FeedNotification, threadMutes: string[]) { - // If there's a subject we want to use that. This will always work on the notifications tab - if (notif.subject) { - const record = notif.subject.record as AppBskyFeedPost.Record - // Check for a quote record - if ( - (record.reply && threadMutes.includes(record.reply.root.uri)) || - (notif.subject.uri && threadMutes.includes(notif.subject.uri)) - ) { - return true - } else if ( - AppBskyEmbedRecord.isMain(record.embed) && - threadMutes.includes(record.embed.record.uri) - ) { - return true - } - } else { - // Otherwise we just do the best that we can - const record = notif.notification.record - if (AppBskyFeedPost.isRecord(record)) { - if (record.reply && threadMutes.includes(record.reply.root.uri)) { - // We can always filter replies - return true - } else if ( - AppBskyEmbedRecord.isMain(record.embed) && - threadMutes.includes(record.embed.record.uri) - ) { - // We can also filter quotes if the quoted post is the root - return true - } - } else if ( - AppBskyFeedRepost.isRecord(record) && - threadMutes.includes(record.subject.uri) - ) { - // Finally we can filter reposts, again if the post is the root - return true - } - } - - return false -} diff --git a/src/state/queries/post.ts b/src/state/queries/post.ts index 794f48eb1b..8e77bf6b92 100644 --- a/src/state/queries/post.ts +++ b/src/state/queries/post.ts @@ -8,6 +8,7 @@ import {logEvent, LogEvents, toClout} from '#/lib/statsig/statsig' import {updatePostShadow} from '#/state/cache/post-shadow' import {Shadow} from '#/state/cache/types' import {useAgent, useSession} from '#/state/session' +import {useIsThreadMuted, useSetThreadMute} from '../cache/thread-mutes' import {findProfileQueryData} from './profile' const RQKEY_ROOT = 'post' @@ -291,3 +292,72 @@ export function usePostDeleteMutation() { }, }) } + +export function useThreadMuteMutationQueue( + post: Shadow, + rootUri: string, +) { + const threadMuteMutation = useThreadMuteMutation() + const threadUnmuteMutation = useThreadUnmuteMutation() + const isThreadMuted = useIsThreadMuted(rootUri, post.viewer?.threadMuted) + const setThreadMute = useSetThreadMute() + + const queueToggle = useToggleMutationQueue({ + initialState: isThreadMuted, + runMutation: async (_prev, shouldLike) => { + if (shouldLike) { + await threadMuteMutation.mutateAsync({ + uri: rootUri, + }) + return true + } else { + await threadUnmuteMutation.mutateAsync({ + uri: rootUri, + }) + return false + } + }, + onSuccess(finalIsMuted) { + // finalize + setThreadMute(rootUri, finalIsMuted) + }, + }) + + const queueMuteThread = useCallback(() => { + // optimistically update + setThreadMute(rootUri, true) + return queueToggle(true) + }, [setThreadMute, rootUri, queueToggle]) + + const queueUnmuteThread = useCallback(() => { + // optimistically update + setThreadMute(rootUri, false) + return queueToggle(false) + }, [rootUri, setThreadMute, queueToggle]) + + return [isThreadMuted, queueMuteThread, queueUnmuteThread] as const +} + +function useThreadMuteMutation() { + const agent = useAgent() + return useMutation< + {}, + Error, + {uri: string} // the root post's uri + >({ + mutationFn: ({uri}) => { + logEvent('post:mute', {}) + return agent.api.app.bsky.graph.muteThread({root: uri}) + }, + }) +} + +function useThreadUnmuteMutation() { + const agent = useAgent() + return useMutation<{}, Error, {uri: string}>({ + mutationFn: ({uri}) => { + logEvent('post:unmute', {}) + return agent.api.app.bsky.graph.unmuteThread({root: uri}) + }, + }) +} diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 2486b73d58..45e00e58c7 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -7,7 +7,7 @@ import { } from 'react-native' import * as Clipboard from 'expo-clipboard' import { - AppBskyActorDefs, + AppBskyFeedDefs, AppBskyFeedPost, AtUri, RichText as RichTextAPI, @@ -22,12 +22,15 @@ import {richTextToString} from '#/lib/strings/rich-text-helpers' import {getTranslatorLink} from '#/locale/helpers' import {logger} from '#/logger' import {isWeb} from '#/platform/detection' +import {Shadow} from '#/state/cache/post-shadow' import {useFeedFeedbackContext} from '#/state/feed-feedback' -import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads' import {useLanguagePrefs} from '#/state/preferences' import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences' import {useOpenLink} from '#/state/preferences/in-app-browser' -import {usePostDeleteMutation} from '#/state/queries/post' +import { + usePostDeleteMutation, + useThreadMuteMutationQueue, +} from '#/state/queries/post' import {useSession} from '#/state/session' import {getCurrentRoute} from 'lib/routes/helpers' import {shareUrl} from 'lib/sharing' @@ -62,9 +65,7 @@ import * as Toast from '../Toast' let PostDropdownBtn = ({ testID, - postAuthor, - postCid, - postUri, + post, postFeedContext, record, richText, @@ -74,9 +75,7 @@ let PostDropdownBtn = ({ timestamp, }: { testID: string - postAuthor: AppBskyActorDefs.ProfileViewBasic - postCid: string - postUri: string + post: Shadow postFeedContext: string | undefined record: AppBskyFeedPost.Record richText: RichTextAPI @@ -92,8 +91,6 @@ let PostDropdownBtn = ({ const {_} = useLingui() const defaultCtrlColor = theme.palette.default.postCtrl const langPrefs = useLanguagePrefs() - const mutedThreads = useMutedThreads() - const toggleThreadMute = useToggleThreadMute() const postDeleteMutation = usePostDeleteMutation() const hiddenPosts = useHiddenPosts() const {hidePost} = useHiddenPostsApi() @@ -107,9 +104,15 @@ let PostDropdownBtn = ({ const loggedOutWarningPromptControl = useDialogControl() const embedPostControl = useDialogControl() const sendViaChatControl = useDialogControl() + const postUri = post.uri + const postCid = post.cid + const postAuthor = post.author const rootUri = record.reply?.root?.uri || postUri - const isThreadMuted = mutedThreads.includes(rootUri) + const [isThreadMuted, muteThread, unmuteThread] = useThreadMuteMutationQueue( + post, + rootUri, + ) const isPostHidden = hiddenPosts && hiddenPosts.includes(postUri) const isAuthor = postAuthor.did === currentAccount?.did @@ -162,18 +165,22 @@ let PostDropdownBtn = ({ const onToggleThreadMute = React.useCallback(() => { try { - const muted = toggleThreadMute(rootUri) - if (muted) { + if (isThreadMuted) { + unmuteThread() + Toast.show(_(msg`You will now receive notifications for this thread`)) + } else { + muteThread() Toast.show( _(msg`You will no longer receive notifications for this thread`), ) - } else { - Toast.show(_(msg`You will now receive notifications for this thread`)) } - } catch (e) { - logger.error('Failed to toggle thread mute', {message: e}) + } catch (e: any) { + if (e?.name !== 'AbortError') { + logger.error('Failed to toggle thread mute', {message: e}) + Toast.show(_(msg`Failed to toggle thread mute, please try again`)) + } } - }, [rootUri, toggleThreadMute, _]) + }, [isThreadMuted, unmuteThread, _, muteThread]) const onCopyPostText = React.useCallback(() => { const str = richTextToString(richText, true) diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index c389855e3d..c0e743db48 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -319,9 +319,7 @@ let PostCtrls = ({ Date: Tue, 18 Jun 2024 11:48:49 -0700 Subject: [PATCH 041/127] Fix: only apply self-thread load-more behavior on the outer edge of the reply tree (#4559) --- src/state/queries/post-thread.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/state/queries/post-thread.ts b/src/state/queries/post-thread.ts index a8b1160fb4..f7e5e2ecba 100644 --- a/src/state/queries/post-thread.ts +++ b/src/state/queries/post-thread.ts @@ -31,6 +31,7 @@ import { getEmbeddedPost, } from './util' +const REPLY_TREE_DEPTH = 10 const RQKEY_ROOT = 'post-thread' export const RQKEY = (uri: string) => [RQKEY_ROOT, uri] type ThreadViewNode = AppBskyFeedGetPostThread.OutputSchema['thread'] @@ -90,7 +91,10 @@ export function usePostThreadQuery(uri: string | undefined) { gcTime: 0, queryKey: RQKEY(uri || ''), async queryFn() { - const res = await agent.getPostThread({uri: uri!, depth: 10}) + const res = await agent.getPostThread({ + uri: uri!, + depth: REPLY_TREE_DEPTH, + }) if (res.success) { const thread = responseToThreadNodes(res.data.thread) annotateSelfThread(thread) @@ -287,7 +291,12 @@ function annotateSelfThread(thread: ThreadNode) { selfThreadNode.ctx.isSelfThread = true } const last = selfThreadNodes[selfThreadNodes.length - 1] - if (last && last.post.replyCount && !last.replies?.length) { + if ( + last && + last.ctx.depth === REPLY_TREE_DEPTH && // at the edge of the tree depth + last.post.replyCount && // has replies + !last.replies?.length // replies were not hydrated + ) { last.ctx.hasMoreSelfThread = true } } From 983d85384b9e736193e6c89107df5ced447a056a Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 18 Jun 2024 13:50:07 -0500 Subject: [PATCH 042/127] Force callers of `getTimeAgo` to pass in the value for "now" (#4560) * Remove icky hook for now * Force callers of getTimeAgo to pass in the 'now' value * Update usage in Newskie dialog --- src/components/NewskieDialog.tsx | 7 ++++--- src/lib/hooks/useTimeAgo.ts | 17 ++++------------- src/view/com/util/TimeElapsed.tsx | 6 ++++-- src/view/screens/Log.tsx | 4 +++- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/components/NewskieDialog.tsx b/src/components/NewskieDialog.tsx index fcdae0daa5..789a42d5f9 100644 --- a/src/components/NewskieDialog.tsx +++ b/src/components/NewskieDialog.tsx @@ -30,12 +30,13 @@ export function NewskieDialog({ const moderation = moderateProfile(profile, moderationOpts) return sanitizeDisplayName(name, moderation.ui('displayName')) }, [moderationOpts, profile]) + const [now] = React.useState(Date.now()) const timeAgo = useGetTimeAgo() const createdAt = profile.createdAt as string | undefined const daysOld = React.useMemo(() => { if (!createdAt) return Infinity - return differenceInSeconds(new Date(), new Date(createdAt)) / 86400 - }, [createdAt]) + return differenceInSeconds(now, new Date(createdAt)) / 86400 + }, [createdAt, now]) if (!createdAt || daysOld > 7) return null @@ -70,7 +71,7 @@ export function NewskieDialog({ {profileName} joined Bluesky{' '} - {timeAgo(createdAt, {format: 'long'})} ago + {timeAgo(createdAt, now, {format: 'long'})} ago diff --git a/src/lib/hooks/useTimeAgo.ts b/src/lib/hooks/useTimeAgo.ts index 5f0782f96f..efcb4754bb 100644 --- a/src/lib/hooks/useTimeAgo.ts +++ b/src/lib/hooks/useTimeAgo.ts @@ -1,4 +1,4 @@ -import {useCallback, useMemo} from 'react' +import {useCallback} from 'react' import {msg, plural} from '@lingui/macro' import {I18nContext, useLingui} from '@lingui/react' import {differenceInSeconds} from 'date-fns' @@ -12,25 +12,16 @@ export function useGetTimeAgo() { const {_} = useLingui() return useCallback( ( - date: number | string | Date, + earlier: number | string | Date, + later: number | string | Date, options?: Omit, ) => { - return dateDiff(date, Date.now(), {lingui: _, format: options?.format}) + return dateDiff(earlier, later, {lingui: _, format: options?.format}) }, [_], ) } -export function useTimeAgo( - date: number | string | Date, - options?: Omit, -): string { - const timeAgo = useGetTimeAgo() - return useMemo(() => { - return timeAgo(date, {...options}) - }, [date, options, timeAgo]) -} - const NOW = 5 const MINUTE = 60 const HOUR = MINUTE * 60 diff --git a/src/view/com/util/TimeElapsed.tsx b/src/view/com/util/TimeElapsed.tsx index d939b3163d..a495851826 100644 --- a/src/view/com/util/TimeElapsed.tsx +++ b/src/view/com/util/TimeElapsed.tsx @@ -15,12 +15,14 @@ export function TimeElapsed({ const ago = useGetTimeAgo() const format = timeToString ?? ago const tick = useTickEveryMinute() - const [timeElapsed, setTimeAgo] = React.useState(() => format(timestamp)) + const [timeElapsed, setTimeAgo] = React.useState(() => + format(timestamp, tick), + ) const [prevTick, setPrevTick] = React.useState(tick) if (prevTick !== tick) { setPrevTick(tick) - setTimeAgo(format(timestamp)) + setTimeAgo(format(timestamp, tick)) } return children({timeElapsed}) diff --git a/src/view/screens/Log.tsx b/src/view/screens/Log.tsx index e10aa83ab7..e6040b77e2 100644 --- a/src/view/screens/Log.tsx +++ b/src/view/screens/Log.tsx @@ -7,6 +7,7 @@ import {useFocusEffect} from '@react-navigation/native' import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {getEntries} from '#/logger/logDump' +import {useTickEveryMinute} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell' import {usePalette} from 'lib/hooks/usePalette' import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' @@ -24,6 +25,7 @@ export function LogScreen({}: NativeStackScreenProps< const setMinimalShellMode = useSetMinimalShellMode() const [expanded, setExpanded] = React.useState([]) const timeAgo = useGetTimeAgo() + const tick = useTickEveryMinute() useFocusEffect( React.useCallback(() => { @@ -72,7 +74,7 @@ export function LogScreen({}: NativeStackScreenProps< /> ) : undefined} - {timeAgo(entry.timestamp)} + {timeAgo(entry.timestamp, tick)} {expanded.includes(entry.id) ? ( From 4165a02b2d712ba20b9fdbf435d4cb00c03e5e52 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 18 Jun 2024 13:52:44 -0500 Subject: [PATCH 043/127] Prevent unecessary calls (#4561) (cherry picked from commit ecb48797675c5be24508bf47141e930c64dac14e) --- src/components/NewskieDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NewskieDialog.tsx b/src/components/NewskieDialog.tsx index 789a42d5f9..281430e31f 100644 --- a/src/components/NewskieDialog.tsx +++ b/src/components/NewskieDialog.tsx @@ -30,7 +30,7 @@ export function NewskieDialog({ const moderation = moderateProfile(profile, moderationOpts) return sanitizeDisplayName(name, moderation.ui('displayName')) }, [moderationOpts, profile]) - const [now] = React.useState(Date.now()) + const [now] = React.useState(() => Date.now()) const timeAgo = useGetTimeAgo() const createdAt = profile.createdAt as string | undefined const daysOld = React.useMemo(() => { From d6ce16d15ae79c4fef943cd48dfa0cdb072e9596 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 18 Jun 2024 12:07:56 -0700 Subject: [PATCH 044/127] Implement thread locking (#4545) * Add the ability to edit threadgates * Fix bottom border on mobile * Refresh thread after threadgate edit --- src/lib/analytics/types.ts | 2 + src/lib/api/index.ts | 17 +- src/state/modals/index.tsx | 3 +- src/state/queries/post-thread.ts | 2 +- src/state/queries/threadgate.ts | 33 +++ src/view/com/modals/Threadgate.tsx | 11 +- src/view/com/post-thread/PostThreadItem.tsx | 25 +- src/view/com/threadgate/WhoCanReply.tsx | 240 ++++++++++++-------- 8 files changed, 222 insertions(+), 111 deletions(-) diff --git a/src/lib/analytics/types.ts b/src/lib/analytics/types.ts index cdf535dec2..720495ea1d 100644 --- a/src/lib/analytics/types.ts +++ b/src/lib/analytics/types.ts @@ -32,6 +32,8 @@ export type TrackPropertiesMap = { 'Post:ThreadMute': {} // CAN BE SERVER 'Post:ThreadUnmute': {} // CAN BE SERVER 'Post:Reply': {} // CAN BE SERVER + 'Post:EditThreadgateOpened': {} + 'Post:ThreadgateEdited': {} // PROFILE events 'Profile:Follow': { username: string diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index dfaae2e018..5b1c998cb8 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -270,7 +270,7 @@ export async function post(agent: BskyAgent, opts: PostOpts) { return res } -async function createThreadgate( +export async function createThreadgate( agent: BskyAgent, postUri: string, threadgate: ThreadgateSetting[], @@ -296,10 +296,17 @@ async function createThreadgate( } const postUrip = new AtUri(postUri) - await agent.api.app.bsky.feed.threadgate.create( - {repo: agent.session!.did, rkey: postUrip.rkey}, - {post: postUri, createdAt: new Date().toISOString(), allow}, - ) + await agent.api.com.atproto.repo.putRecord({ + repo: agent.session!.did, + collection: 'app.bsky.feed.threadgate', + rkey: postUrip.rkey, + record: { + $type: 'app.bsky.feed.threadgate', + post: postUri, + allow, + createdAt: new Date().toISOString(), + }, + }) } // helpers diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index ced14335bd..685b10bd85 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -70,7 +70,8 @@ export interface SelfLabelModal { export interface ThreadgateModal { name: 'threadgate' settings: ThreadgateSetting[] - onChange: (settings: ThreadgateSetting[]) => void + onChange?: (settings: ThreadgateSetting[]) => void + onConfirm?: (settings: ThreadgateSetting[]) => void } export interface ChangeHandleModal { diff --git a/src/state/queries/post-thread.ts b/src/state/queries/post-thread.ts index f7e5e2ecba..db85e8a177 100644 --- a/src/state/queries/post-thread.ts +++ b/src/state/queries/post-thread.ts @@ -32,7 +32,7 @@ import { } from './util' const REPLY_TREE_DEPTH = 10 -const RQKEY_ROOT = 'post-thread' +export const RQKEY_ROOT = 'post-thread' export const RQKEY = (uri: string) => [RQKEY_ROOT, uri] type ThreadViewNode = AppBskyFeedGetPostThread.OutputSchema['thread'] diff --git a/src/state/queries/threadgate.ts b/src/state/queries/threadgate.ts index 4891175825..67c6f8c084 100644 --- a/src/state/queries/threadgate.ts +++ b/src/state/queries/threadgate.ts @@ -1,5 +1,38 @@ +import {AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api' + export type ThreadgateSetting = | {type: 'nobody'} | {type: 'mention'} | {type: 'following'} | {type: 'list'; list: string} + +export function threadgateViewToSettings( + threadgate: AppBskyFeedDefs.ThreadgateView | undefined, +): ThreadgateSetting[] { + const record = + threadgate && + AppBskyFeedThreadgate.isRecord(threadgate.record) && + AppBskyFeedThreadgate.validateRecord(threadgate.record).success + ? threadgate.record + : null + if (!record) { + return [] + } + if (!record.allow?.length) { + return [{type: 'nobody'}] + } + return record.allow + .map(allow => { + if (allow.$type === 'app.bsky.feed.threadgate#mentionRule') { + return {type: 'mention'} + } + if (allow.$type === 'app.bsky.feed.threadgate#followingRule') { + return {type: 'following'} + } + if (allow.$type === 'app.bsky.feed.threadgate#listRule') { + return {type: 'list', list: allow.list} + } + return undefined + }) + .filter(Boolean) as ThreadgateSetting[] +} diff --git a/src/view/com/modals/Threadgate.tsx b/src/view/com/modals/Threadgate.tsx index a2e9f391c0..4a9a9e2ab5 100644 --- a/src/view/com/modals/Threadgate.tsx +++ b/src/view/com/modals/Threadgate.tsx @@ -26,9 +26,11 @@ export const snapPoints = ['60%'] export function Component({ settings, onChange, + onConfirm, }: { settings: ThreadgateSetting[] - onChange: (settings: ThreadgateSetting[]) => void + onChange?: (settings: ThreadgateSetting[]) => void + onConfirm?: (settings: ThreadgateSetting[]) => void }) { const pal = usePalette('default') const {closeModal} = useModalControls() @@ -38,12 +40,12 @@ export function Component({ const onPressEverybody = () => { setSelected([]) - onChange([]) + onChange?.([]) } const onPressNobody = () => { setSelected([{type: 'nobody'}]) - onChange([{type: 'nobody'}]) + onChange?.([{type: 'nobody'}]) } const onPressAudience = (setting: ThreadgateSetting) => { @@ -57,7 +59,7 @@ export function Component({ newSelected.splice(i, 1) } setSelected(newSelected) - onChange(newSelected) + onChange?.(newSelected) } return ( @@ -124,6 +126,7 @@ export function Component({ testID="confirmBtn" onPress={() => { closeModal() + onConfirm?.(selected) }} style={styles.btn} accessibilityRole="button" diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 5ee60e4eab..6d03029d7f 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -25,7 +25,7 @@ import {sanitizeHandle} from 'lib/strings/handles' import {countLines} from 'lib/strings/helpers' import {niceDate} from 'lib/strings/time' import {s} from 'lib/styles' -import {isWeb} from 'platform/detection' +import {isNative, isWeb} from 'platform/detection' import {useSession} from 'state/session' import {PostThreadFollowBtn} from 'view/com/post-thread/PostThreadFollowBtn' import {atoms as a} from '#/alf' @@ -189,6 +189,7 @@ let PostThreadItemLoaded = ({ const itemTitle = _(msg`Post by ${post.author.handle}`) const authorHref = makeProfileLink(post.author) const authorTitle = post.author.handle + const isThreadAuthor = getThreadAuthor(post, record) === currentAccount?.did const likesHref = React.useMemo(() => { const urip = new AtUri(post.uri) return makeProfileLink(post.author, 'post', urip.rkey, 'liked-by') @@ -395,7 +396,11 @@ let PostThreadItemLoaded = ({ - + ) } else { @@ -578,7 +583,9 @@ let PostThreadItemLoaded = ({ post={post} style={{ marginTop: 4, + borderBottomWidth: 1, }} + isThreadAuthor={isThreadAuthor} /> ) @@ -681,6 +688,20 @@ function ExpandedPostDetails({ ) } +function getThreadAuthor( + post: AppBskyFeedDefs.PostView, + record: AppBskyFeedPost.Record, +): string { + if (!record.reply) { + return post.author.did + } + try { + return new AtUri(record.reply.root.uri).host + } catch { + return '' + } +} + const styles = StyleSheet.create({ outer: { borderTopWidth: hairlineWidth, diff --git a/src/view/com/threadgate/WhoCanReply.tsx b/src/view/com/threadgate/WhoCanReply.tsx index c1e36d481d..3ffbaa7ae9 100644 --- a/src/view/com/threadgate/WhoCanReply.tsx +++ b/src/view/com/threadgate/WhoCanReply.tsx @@ -1,128 +1,172 @@ import React from 'react' -import {StyleProp, View, ViewStyle} from 'react-native' -import { - AppBskyFeedDefs, - AppBskyFeedThreadgate, - AppBskyGraphDefs, - AtUri, -} from '@atproto/api' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Trans} from '@lingui/macro' +import {Keyboard, StyleProp, View, ViewStyle} from 'react-native' +import {AppBskyFeedDefs, AppBskyGraphDefs, AtUri} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' +import {useAnalytics} from '#/lib/analytics/analytics' +import {createThreadgate} from '#/lib/api' import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {makeListLink, makeProfileLink} from '#/lib/routes/links' import {colors} from '#/lib/styles' +import {logger} from '#/logger' +import {isNative} from '#/platform/detection' +import {useModalControls} from '#/state/modals' +import {RQKEY_ROOT as POST_THREAD_RQKEY_ROOT} from '#/state/queries/post-thread' +import { + ThreadgateSetting, + threadgateViewToSettings, +} from '#/state/queries/threadgate' +import {useAgent} from '#/state/session' +import * as Toast from 'view/com/util/Toast' +import {Button} from '#/components/Button' import {TextLink} from '../util/Link' import {Text} from '../util/text/Text' export function WhoCanReply({ post, + isThreadAuthor, style, }: { post: AppBskyFeedDefs.PostView + isThreadAuthor: boolean style?: StyleProp }) { + const {track} = useAnalytics() + const {_} = useLingui() const pal = usePalette('default') - const {isMobile} = useWebMediaQueries() + const agent = useAgent() + const queryClient = useQueryClient() + const {openModal} = useModalControls() const containerStyles = useColorSchemeStyle( { - borderColor: pal.colors.unreadNotifBorder, backgroundColor: pal.colors.unreadNotifBg, }, { - borderColor: pal.colors.unreadNotifBorder, backgroundColor: pal.colors.unreadNotifBg, }, ) - const iconStyles = useColorSchemeStyle( - { - backgroundColor: colors.blue3, - }, - { - backgroundColor: colors.blue3, - }, - ) const textStyles = useColorSchemeStyle( - {color: colors.gray7}, + {color: colors.blue5}, {color: colors.blue1}, ) - const record = React.useMemo( - () => - post.threadgate && - AppBskyFeedThreadgate.isRecord(post.threadgate.record) && - AppBskyFeedThreadgate.validateRecord(post.threadgate.record).success - ? post.threadgate.record - : null, + const hoverStyles = useColorSchemeStyle( + { + backgroundColor: colors.white, + }, + { + backgroundColor: pal.colors.background, + }, + ) + const settings = React.useMemo( + () => threadgateViewToSettings(post.threadgate), [post], ) - if (record) { - return ( - - - - - - - {!record.allow?.length ? ( - Replies to this thread are disabled - ) : ( - - Only{' '} - {record.allow.map((rule, i) => ( - <> - - - - ))}{' '} - can reply. - - )} - - - - ) + const isRootPost = !('reply' in post.record) + + const onPressEdit = () => { + track('Post:EditThreadgateOpened') + if (isNative && Keyboard.isVisible()) { + Keyboard.dismiss() + } + openModal({ + name: 'threadgate', + settings, + async onConfirm(newSettings: ThreadgateSetting[]) { + try { + if (newSettings.length) { + await createThreadgate(agent, post.uri, newSettings) + } else { + await agent.api.com.atproto.repo.deleteRecord({ + repo: agent.session!.did, + collection: 'app.bsky.feed.threadgate', + rkey: new AtUri(post.uri).rkey, + }) + } + Toast.show('Thread settings updated') + queryClient.invalidateQueries({ + queryKey: [POST_THREAD_RQKEY_ROOT], + }) + track('Post:ThreadgateEdited') + } catch (err) { + Toast.show( + 'There was an issue. Please check your internet connection and try again.', + ) + logger.error('Failed to edit threadgate', {message: err}) + } + }, + }) } - return null + + if (!isRootPost) { + return null + } + if (!settings.length && !isThreadAuthor) { + return null + } + + return ( + + + + {!settings.length ? ( + Everybody can reply. + ) : settings[0].type === 'nobody' ? ( + Replies to this thread are disabled. + ) : ( + + Only{' '} + {settings.map((rule, i) => ( + <> + + + + ))}{' '} + can reply. + + )} + + + {isThreadAuthor && ( + + + + )} + + ) } function Rule({ @@ -130,15 +174,15 @@ function Rule({ post, lists, }: { - rule: any + rule: ThreadgateSetting post: AppBskyFeedDefs.PostView lists: AppBskyGraphDefs.ListViewBasic[] | undefined }) { const pal = usePalette('default') - if (AppBskyFeedThreadgate.isMentionRule(rule)) { + if (rule.type === 'mention') { return mentioned users } - if (AppBskyFeedThreadgate.isFollowingRule(rule)) { + if (rule.type === 'following') { return ( users followed by{' '} @@ -151,7 +195,7 @@ function Rule({ ) } - if (AppBskyFeedThreadgate.isListRule(rule)) { + if (rule.type === 'list') { const list = lists?.find(l => l.uri === rule.list) if (list) { const listUrip = new AtUri(list.uri) From 502bcad7017d72fb23c40a268b1e220f892db7da Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 18 Jun 2024 14:09:40 -0500 Subject: [PATCH 045/127] Disable newskie dialog tap in hover card web (#4562) --- src/components/NewskieDialog.tsx | 3 +++ src/components/ProfileHoverCard/index.web.tsx | 2 +- src/screens/Profile/Header/Handle.tsx | 6 ++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/NewskieDialog.tsx b/src/components/NewskieDialog.tsx index 281430e31f..0354bfc432 100644 --- a/src/components/NewskieDialog.tsx +++ b/src/components/NewskieDialog.tsx @@ -18,8 +18,10 @@ import {Text} from '#/components/Typography' export function NewskieDialog({ profile, + disabled, }: { profile: AppBskyActorDefs.ProfileViewDetailed + disabled?: boolean }) { const {_} = useLingui() const moderationOpts = useModerationOpts() @@ -43,6 +45,7 @@ export function NewskieDialog({ return ( diff --git a/src/view/com/util/post-ctrls/RepostButton.web.tsx b/src/view/com/util/post-ctrls/RepostButton.web.tsx index 0898981419..17ab736ced 100644 --- a/src/view/com/util/post-ctrls/RepostButton.web.tsx +++ b/src/view/com/util/post-ctrls/RepostButton.web.tsx @@ -12,6 +12,7 @@ import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repos import * as Menu from '#/components/Menu' import {Text} from '#/components/Typography' import {EventStopper} from '../EventStopper' +import {formatCount} from '../numeric/format' interface Props { isReposted: boolean @@ -115,20 +116,22 @@ const RepostInner = ({ color: {color: string} repostCount?: number big?: boolean -}) => ( - - - {typeof repostCount !== 'undefined' && repostCount > 0 ? ( - - {repostCount} - - ) : undefined} - -) +}) => { + return ( + + + {typeof repostCount !== 'undefined' && repostCount > 0 ? ( + + {formatCount(repostCount)} + + ) : undefined} + + ) +} diff --git a/yarn.lock b/yarn.lock index 700ddfe072..a0fd8749a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3897,18 +3897,18 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== -"@formatjs/ecma402-abstract@1.18.0": - version "1.18.0" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz#e2120e7101020140661b58430a7ff4262705a2f2" - integrity sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA== +"@formatjs/ecma402-abstract@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz#39197ab90b1c78b7342b129a56a7acdb8f512e17" + integrity sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g== dependencies: - "@formatjs/intl-localematcher" "0.5.2" + "@formatjs/intl-localematcher" "0.5.4" tslib "^2.4.0" -"@formatjs/intl-enumerator@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@formatjs/intl-enumerator/-/intl-enumerator-1.4.3.tgz#8d278c273485d7c6219916509fbd51ce3142064d" - integrity sha512-0NpTmAQnDokPoB5aVtXvOdtrUq/uEuPPhBUAr57TYYDjI5MwfFXt8F6JCm6s6CPI0inL8+nxPLjjqH0qyNnP4Q== +"@formatjs/intl-enumerator@1.4.7": + version "1.4.7" + resolved "https://registry.yarnpkg.com/@formatjs/intl-enumerator/-/intl-enumerator-1.4.7.tgz#6ab697f3f8f18cf0cc6a6b028cb9c40db6001f3d" + integrity sha512-03RHnFqfpB4H/jwCwlzC+wkTDk2Fi24JmVIY2PVGvTUpikN2bSr9+8oTXfOC+y7B7VxjCArUnqWXVoctkmy85w== dependencies: tslib "^2.4.0" @@ -3919,30 +3919,39 @@ dependencies: tslib "^2.4.0" -"@formatjs/intl-locale@^3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@formatjs/intl-locale/-/intl-locale-3.4.3.tgz#fdd2a3978b03aa76965abbca86526bb1d02973b6" - integrity sha512-g/35yMikkkRmLYmqE4W74gvZyKa768oC9OmUFzfLmH3CVYF3v2kvAZI0WsxWLbxYj8TT7wBDeLIL3aIlRw4Osw== +"@formatjs/intl-locale@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@formatjs/intl-locale/-/intl-locale-4.0.0.tgz#c111a33078413eba2011e82140466261eb1d67cd" + integrity sha512-+4dbMEGsp1bvB3JB3UHH6YTjMnFTifnfdaHp4ROrCCu50NedA69RBsDCG3eivcZkbj57X9ehGhMWjLxlP+gyVw== dependencies: - "@formatjs/ecma402-abstract" "1.18.0" - "@formatjs/intl-enumerator" "1.4.3" + "@formatjs/ecma402-abstract" "2.0.0" + "@formatjs/intl-enumerator" "1.4.7" "@formatjs/intl-getcanonicallocales" "2.3.0" tslib "^2.4.0" -"@formatjs/intl-localematcher@0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz#5fcf029fd218905575e5080fa33facdcb623d532" - integrity sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw== +"@formatjs/intl-localematcher@0.5.4": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz#caa71f2e40d93e37d58be35cfffe57865f2b366f" + integrity sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g== dependencies: tslib "^2.4.0" -"@formatjs/intl-pluralrules@^5.2.10": - version "5.2.10" - resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-5.2.10.tgz#379fc06133625df0cae715c1d902001974ff3279" - integrity sha512-wfJypePrbOByaZVPP1moLXHgS9LeAvi9coP95XZX7ySVrwdDGPnxz9Pw+o7J1o8AjLxjiqGrvAi74key5zzIjQ== +"@formatjs/intl-numberformat@^8.10.3": + version "8.10.3" + resolved "https://registry.yarnpkg.com/@formatjs/intl-numberformat/-/intl-numberformat-8.10.3.tgz#abc97cc6a7b7f1b20da9f07a976b5589c1192ab8" + integrity sha512-lH3liLMeIjZ19Zxt8RRPnBcpPweS1YNSXRURDiFfvFmRlDZUOd8+GlcVyECcPZPkIoSH/p4lfGrnaUzepxJ92g== dependencies: - "@formatjs/ecma402-abstract" "1.18.0" - "@formatjs/intl-localematcher" "0.5.2" + "@formatjs/ecma402-abstract" "2.0.0" + "@formatjs/intl-localematcher" "0.5.4" + tslib "^2.4.0" + +"@formatjs/intl-pluralrules@^5.2.14": + version "5.2.14" + resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-5.2.14.tgz#7477bd2aa9bfde9e543d839707eff5460eb08026" + integrity sha512-l6Ev7aOGXJSh5EPDEqzsbyufdCCKXZk993QXRQebLsB0TXRhIyF4alqjdMEatLwIigK/Mka8kiVIOLeFP5Cj9Q== + dependencies: + "@formatjs/ecma402-abstract" "2.0.0" + "@formatjs/intl-localematcher" "0.5.4" tslib "^2.4.0" "@fortawesome/fontawesome-common-types@6.4.2": From a98c4efc7cbb4dc276d27f9db5abc44790892501 Mon Sep 17 00:00:00 2001 From: Kuwa Lee Date: Thu, 20 Jun 2024 05:24:00 +0800 Subject: [PATCH 061/127] TW: Update and clean --- src/locale/locales/zh-TW/messages.po | 378 ++++++++++++++++----------- 1 file changed, 221 insertions(+), 157 deletions(-) diff --git a/src/locale/locales/zh-TW/messages.po b/src/locale/locales/zh-TW/messages.po index ba5a258b89..49a020533f 100644 --- a/src/locale/locales/zh-TW/messages.po +++ b/src/locale/locales/zh-TW/messages.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: zh-TW for bluesky-social-app\n" "POT-Creation-Date: \n" "Report-Msgid-Bugs-To: Kuwa Lee , Frudrax Cheng \n" -"PO-Revision-Date: 2024-06-16 20:31+0800\n" +"PO-Revision-Date: 2024-06-20 05:23+0800\n" "Last-Translator: \n" "Language-Team: Frudrax Cheng , Kuwa Lee , noeFly, snowleo208, Kisaragi Hiu, Yi-Jyun Pan, toto6038, cirx1e\n" "Language: zh_TW\n" @@ -21,7 +21,7 @@ msgstr "(含有嵌入內容)" msgid "(no email)" msgstr "(沒有電子郵件)" -#: src/view/com/notifications/FeedItem.tsx:261 +#: src/view/com/notifications/FeedItem.tsx:263 msgid "{0, plural, one {{formattedCount} other} other {{formattedCount} others}}" msgstr "{0, plural, one {其他 {formattedCount} 個人} other {其他 {formattedCount} 個人}}" @@ -51,11 +51,11 @@ msgstr "{0, plural, one {個跟隨中} other {個跟隨中}}" msgid "{0, plural, one {Like (# like)} other {Like (# likes)}}" msgstr "{0, plural, one {喜歡(# 個喜歡)} other {喜歡(# 個喜歡)}}" -#: src/view/com/post-thread/PostThreadItem.tsx:380 +#: src/view/com/post-thread/PostThreadItem.tsx:381 msgid "{0, plural, one {like} other {likes}}" msgstr "{0, plural, one {喜歡} other {喜歡}}" -#: src/components/FeedCard.tsx:110 +#: src/components/FeedCard.tsx:111 #: src/view/com/feeds/FeedSourceCard.tsx:301 msgid "{0, plural, one {Liked by # user} other {Liked by # users}}" msgstr "{0, plural,one {# 個用戶已喜歡} other {# 個用戶已喜歡}}" @@ -68,7 +68,7 @@ msgstr "{0, plural, one {則貼文} other {則貼文}}" msgid "{0, plural, one {Reply (# reply)} other {Reply (# replies)}}" msgstr "{0, plural, one {回覆(# 個回覆)} other {回覆(# 個回覆)}}" -#: src/view/com/post-thread/PostThreadItem.tsx:360 +#: src/view/com/post-thread/PostThreadItem.tsx:361 msgid "{0, plural, one {repost} other {reposts}}" msgstr "{0, plural, one {轉貼} other {轉貼}}" @@ -76,7 +76,7 @@ msgstr "{0, plural, one {轉貼} other {轉貼}}" msgid "{0, plural, one {Unlike (# like)} other {Unlike (# likes)}}" msgstr "{0, plural, one {撤回喜歡(# 個喜歡)} other {撤回喜歡(# 個喜歡)}}" -#: src/view/com/util/UserAvatar.tsx:406 +#: src/view/com/util/UserAvatar.tsx:419 msgid "{0}'s avatar" msgstr "{0} 的頭像" @@ -84,6 +84,26 @@ msgstr "{0} 的頭像" msgid "{count, plural, one {Liked by # user} other {Liked by # users}}" msgstr "{count, plural, one {# 個用戶已喜歡} other {# 個用戶已喜歡}}" +#: src/lib/hooks/useTimeAgo.ts:69 +msgid "{diff, plural, one {day} other {days}}" +msgstr "{diff, plural, one {天} other {天}}" + +#: src/lib/hooks/useTimeAgo.ts:64 +msgid "{diff, plural, one {hour} other {hours}}" +msgstr "{diff, plural, one {時} other {時}}" + +#: src/lib/hooks/useTimeAgo.ts:59 +msgid "{diff, plural, one {minute} other {minutes}}" +msgstr "{diff, plural, one {分} other {分}}" + +#: src/lib/hooks/useTimeAgo.ts:75 +msgid "{diff, plural, one {month} other {months}}" +msgstr "{diff, plural, one {月} other {月}}" + +#: src/lib/hooks/useTimeAgo.ts:54 +msgid "{diffSeconds, plural, one {second} other {seconds}}" +msgstr "{diffSeconds, plural, one {秒} other {秒}}" + #: src/screens/SignupQueued.tsx:207 msgid "{estimatedTimeHrs, plural, one {hour} other {hours}}" msgstr "{estimatedTimeHrs, plural, one {時} other {時}}" @@ -111,11 +131,15 @@ msgstr "{likeCount, plural, one {# 個用戶已喜歡} other {# 個用戶已喜 msgid "{numUnreadNotifications} unread" msgstr "{numUnreadNotifications} 個未讀通知" +#: src/components/NewskieDialog.tsx:75 +msgid "{profileName} joined Bluesky {0} ago" +msgstr "{profileName} 在 {0} 前加入了 Bluesky" + #: src/view/screens/PreferencesFollowingFeed.tsx:67 msgid "{value, plural, =0 {Show all replies} one {Show replies with at least # like} other {Show replies with at least # likes}}" msgstr "{value, plural, =0 {顯示所有回覆} one {顯示至少 # 個喜歡的回覆} other {顯示至少 # 個喜歡的回覆}}" -#: src/view/com/threadgate/WhoCanReply.tsx:159 +#: src/view/com/threadgate/WhoCanReply.tsx:203 msgid "<0/> members" msgstr "<0/> 個成員" @@ -131,7 +155,7 @@ msgstr "<0>{0} {1, plural, one {個跟隨中} other {個跟隨中}}" msgid "<0>Not Applicable. This warning is only available for posts with media attached." msgstr "<0>不適用。 此警告只適用於附帶媒體的貼文。" -#: src/screens/Profile/Header/Handle.tsx:43 +#: src/screens/Profile/Header/Handle.tsx:50 msgid "⚠Invalid Handle" msgstr "⚠無效的帳號代碼" @@ -140,7 +164,7 @@ msgid "2FA Confirmation" msgstr "雙重驗證" #: src/view/com/util/ViewHeader.tsx:93 -#: src/view/screens/Search/Search.tsx:682 +#: src/view/screens/Search/Search.tsx:684 msgid "Access navigation links and settings" msgstr "存取導覽連結和設定" @@ -267,7 +291,7 @@ msgstr "新增預設的「Following」動態源,它只會顯示您跟隨的人 msgid "Add the following DNS record to your domain:" msgstr "將以下 DNS 記錄新增到您的網域:" -#: src/components/FeedCard.tsx:173 +#: src/components/FeedCard.tsx:180 msgid "Add this feed to your feeds" msgstr "將此新增至您的動態源" @@ -380,8 +404,8 @@ msgstr "出現問題,請再試一次。" msgid "an unknown error occurred" msgstr "出現未知錯誤" -#: src/view/com/notifications/FeedItem.tsx:258 -#: src/view/com/threadgate/WhoCanReply.tsx:180 +#: src/view/com/notifications/FeedItem.tsx:260 +#: src/view/com/threadgate/WhoCanReply.tsx:224 msgid "and" msgstr "和" @@ -469,7 +493,7 @@ msgstr "您確定要離開此對話嗎?您的訊息將為您刪除,但不會 msgid "Are you sure you want to remove {0} from your feeds?" msgstr "您確定要從您的動態中移除 {0} 嗎?" -#: src/components/FeedCard.tsx:190 +#: src/components/FeedCard.tsx:197 msgid "Are you sure you want to remove this from your feeds?" msgstr "您確定要將此從您的動態源中移除嗎?" @@ -581,7 +605,7 @@ msgstr "被封鎖的帳號無法在您的討論串中回覆、提及您,或以 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours." msgstr "被封鎖的帳號無法在您的討論串中回覆、提及您,或以其他方式與您互動。您將看不到他們的內容,他們也會被阻止看到您的內容。" -#: src/view/com/post-thread/PostThread.tsx:363 +#: src/view/com/post-thread/PostThread.tsx:367 msgid "Blocked post." msgstr "已封鎖貼文。" @@ -686,7 +710,7 @@ msgstr "只能包含字母、數字、空格、破折號及底線。長度必須 #: src/view/com/modals/VerifyEmail.tsx:255 #: src/view/com/modals/VerifyEmail.tsx:261 #: src/view/com/util/post-ctrls/RepostButton.tsx:138 -#: src/view/screens/Search/Search.tsx:702 +#: src/view/screens/Search/Search.tsx:704 #: src/view/shell/desktop/Search.tsx:218 msgid "Cancel" msgstr "取消" @@ -811,7 +835,7 @@ msgstr "在此輸入寄送至您電子郵件地址的驗證碼。" msgid "Check your inbox for an email with the confirmation code to enter below:" msgstr "在下方輸入寄送至您電子郵件地址的驗證碼:" -#: src/view/com/modals/Threadgate.tsx:73 +#: src/view/com/modals/Threadgate.tsx:75 msgid "Choose \"Everybody\" or \"Nobody\"" msgstr "選擇「所有人」或「沒有人」" @@ -848,7 +872,7 @@ msgid "Clear all storage data (restart after this)" msgstr "清除所有資料(並重啟)" #: src/view/com/util/forms/SearchInput.tsx:88 -#: src/view/screens/Search/Search.tsx:828 +#: src/view/screens/Search/Search.tsx:824 msgid "Clear search query" msgstr "清除搜尋記錄" @@ -956,11 +980,11 @@ msgstr "關閉貼文編輯頁並捨棄草稿" msgid "Closes viewer for header image" msgstr "關閉標題圖片檢視器" -#: src/view/com/notifications/FeedItem.tsx:205 +#: src/view/com/notifications/FeedItem.tsx:207 msgid "Collapse list of users" msgstr "折疊用戶清單" -#: src/view/com/notifications/FeedItem.tsx:341 +#: src/view/com/notifications/FeedItem.tsx:343 msgid "Collapses list of users for a given notification" msgstr "折疊指定通知的用戶清單" @@ -1125,7 +1149,7 @@ msgstr "已複製建構版本號至剪貼簿" #: src/view/com/modals/AddAppPasswords.tsx:80 #: src/view/com/modals/ChangeHandle.tsx:320 #: src/view/com/modals/InviteCodes.tsx:153 -#: src/view/com/util/forms/PostDropdownBtn.tsx:182 +#: src/view/com/util/forms/PostDropdownBtn.tsx:189 msgid "Copied to clipboard" msgstr "已複製至剪貼簿" @@ -1154,8 +1178,8 @@ msgstr "複製程式碼" msgid "Copy link to list" msgstr "複製列表連結" -#: src/view/com/util/forms/PostDropdownBtn.tsx:300 -#: src/view/com/util/forms/PostDropdownBtn.tsx:309 +#: src/view/com/util/forms/PostDropdownBtn.tsx:307 +#: src/view/com/util/forms/PostDropdownBtn.tsx:316 msgid "Copy link to post" msgstr "複製貼文連結" @@ -1164,8 +1188,8 @@ msgstr "複製貼文連結" msgid "Copy message text" msgstr "複製訊息文字" -#: src/view/com/util/forms/PostDropdownBtn.tsx:278 -#: src/view/com/util/forms/PostDropdownBtn.tsx:280 +#: src/view/com/util/forms/PostDropdownBtn.tsx:285 +#: src/view/com/util/forms/PostDropdownBtn.tsx:287 msgid "Copy post text" msgstr "複製貼文文字" @@ -1243,7 +1267,7 @@ msgid "Custom domain" msgstr "自訂網域" #: src/view/screens/Feeds.tsx:763 -#: src/view/screens/Search/Explore.tsx:380 +#: src/view/screens/Search/Explore.tsx:383 msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." msgstr "由社群打造的自訂動態源帶來全新體驗,幫助您找到所愛的內容。" @@ -1286,7 +1310,7 @@ msgid "Debug panel" msgstr "偵錯面板" #: src/components/dms/MessageMenu.tsx:151 -#: src/view/com/util/forms/PostDropdownBtn.tsx:426 +#: src/view/com/util/forms/PostDropdownBtn.tsx:433 #: src/view/screens/AppPasswords.tsx:285 #: src/view/screens/ProfileList.tsx:667 msgid "Delete" @@ -1337,8 +1361,8 @@ msgstr "刪除我的帳號" msgid "Delete My Account…" msgstr "刪除我的帳號…" -#: src/view/com/util/forms/PostDropdownBtn.tsx:407 -#: src/view/com/util/forms/PostDropdownBtn.tsx:409 +#: src/view/com/util/forms/PostDropdownBtn.tsx:414 +#: src/view/com/util/forms/PostDropdownBtn.tsx:416 msgid "Delete post" msgstr "刪除貼文" @@ -1346,7 +1370,7 @@ msgstr "刪除貼文" msgid "Delete this list?" msgstr "刪除此列表?" -#: src/view/com/util/forms/PostDropdownBtn.tsx:421 +#: src/view/com/util/forms/PostDropdownBtn.tsx:428 msgid "Delete this post?" msgstr "刪除這條貼文?" @@ -1354,7 +1378,7 @@ msgstr "刪除這條貼文?" msgid "Deleted" msgstr "已刪除" -#: src/view/com/post-thread/PostThread.tsx:349 +#: src/view/com/post-thread/PostThread.tsx:353 msgid "Deleted post." msgstr "已刪除的貼文。" @@ -1424,7 +1448,7 @@ msgstr "阻撓應用程式向未登入用戶顯示我的帳號" msgid "Discover new custom feeds" msgstr "探索新的自訂動態源" -#: src/view/screens/Search/Explore.tsx:378 +#: src/view/screens/Search/Explore.tsx:381 msgid "Discover new feeds" msgstr "探索新的動態源" @@ -1462,8 +1486,8 @@ msgstr "網域已驗證!" #: src/components/dialogs/BirthDateSettings.tsx:119 #: src/components/dialogs/BirthDateSettings.tsx:125 -#: src/components/forms/DateField/index.tsx:74 -#: src/components/forms/DateField/index.tsx:80 +#: src/components/forms/DateField/index.tsx:77 +#: src/components/forms/DateField/index.tsx:83 #: src/screens/Onboarding/StepProfile/index.tsx:322 #: src/screens/Onboarding/StepProfile/index.tsx:325 #: src/view/com/auth/server-input/index.tsx:169 @@ -1481,8 +1505,8 @@ msgstr "完成" #: src/view/com/modals/EditImage.tsx:334 #: src/view/com/modals/ListAddRemoveUsers.tsx:144 #: src/view/com/modals/SelfLabel.tsx:158 -#: src/view/com/modals/Threadgate.tsx:130 #: src/view/com/modals/Threadgate.tsx:133 +#: src/view/com/modals/Threadgate.tsx:136 #: src/view/com/modals/UserAddRemoveLists.tsx:108 #: src/view/com/modals/UserAddRemoveLists.tsx:111 #: src/view/screens/PreferencesThreads.tsx:162 @@ -1548,12 +1572,14 @@ msgctxt "action" msgid "Edit" msgstr "編輯" +#: src/view/com/threadgate/WhoCanReply.tsx:153 +#: src/view/com/threadgate/WhoCanReply.tsx:161 #: src/view/screens/Feeds.tsx:370 #: src/view/screens/Feeds.tsx:441 msgid "Edit" msgstr "編輯" -#: src/view/com/util/UserAvatar.tsx:312 +#: src/view/com/util/UserAvatar.tsx:325 #: src/view/com/util/UserBanner.tsx:92 msgid "Edit avatar" msgstr "編輯頭像" @@ -1643,8 +1669,8 @@ msgid "Embed HTML code" msgstr "嵌入 HTML 程式碼" #: src/components/dialogs/Embed.tsx:97 -#: src/view/com/util/forms/PostDropdownBtn.tsx:317 -#: src/view/com/util/forms/PostDropdownBtn.tsx:319 +#: src/view/com/util/forms/PostDropdownBtn.tsx:324 +#: src/view/com/util/forms/PostDropdownBtn.tsx:326 msgid "Embed post" msgstr "嵌入貼文" @@ -1750,7 +1776,7 @@ msgstr "Captcha 給出了錯誤的回應。" msgid "Error:" msgstr "錯誤:" -#: src/view/com/modals/Threadgate.tsx:77 +#: src/view/com/modals/Threadgate.tsx:79 msgid "Everybody" msgstr "所有人" @@ -1758,6 +1784,10 @@ msgstr "所有人" msgid "Everybody can reply" msgstr "所有人都可以回覆" +#: src/view/com/threadgate/WhoCanReply.tsx:129 +msgid "Everybody can reply." +msgstr "所有人都可以回覆。" + #: src/components/dms/MessagesNUX.tsx:131 #: src/components/dms/MessagesNUX.tsx:134 #: src/screens/Messages/Settings.tsx:75 @@ -1798,7 +1828,7 @@ msgstr "退出輸入搜索查詢" msgid "Expand alt text" msgstr "展開替代文字" -#: src/view/com/notifications/FeedItem.tsx:206 +#: src/view/com/notifications/FeedItem.tsx:208 msgid "Expand list of users" msgstr "展開用戶清單" @@ -1857,12 +1887,12 @@ msgstr "無法建立列表。請檢查您的網路連線並重試。" msgid "Failed to delete message" msgstr "無法刪除訊息" -#: src/view/com/util/forms/PostDropdownBtn.tsx:149 +#: src/view/com/util/forms/PostDropdownBtn.tsx:152 msgid "Failed to delete post, please try again" msgstr "無法刪除貼文,請重試" -#: src/view/screens/Search/Explore.tsx:414 -#: src/view/screens/Search/Explore.tsx:438 +#: src/view/screens/Search/Explore.tsx:417 +#: src/view/screens/Search/Explore.tsx:441 msgid "Failed to load feeds preferences" msgstr "無法載入動態源偏好" @@ -1875,12 +1905,12 @@ msgstr "無法載入 GIF" msgid "Failed to load past messages" msgstr "無法載入過去的訊息" -#: src/view/screens/Search/Explore.tsx:407 -#: src/view/screens/Search/Explore.tsx:431 +#: src/view/screens/Search/Explore.tsx:410 +#: src/view/screens/Search/Explore.tsx:434 msgid "Failed to load suggested feeds" msgstr "無法載入建議的動態源" -#: src/view/screens/Search/Explore.tsx:367 +#: src/view/screens/Search/Explore.tsx:370 msgid "Failed to load suggested follows" msgstr "無法載入建議的跟隨者" @@ -1897,7 +1927,11 @@ msgstr "無法傳送" msgid "Failed to submit appeal, please try again." msgstr "無法提交申訴,請重試。" -#: src/components/FeedCard.tsx:153 +#: src/view/com/util/forms/PostDropdownBtn.tsx:180 +msgid "Failed to toggle thread mute, please try again" +msgstr "無法將討論串設為靜音,請重試" + +#: src/components/FeedCard.tsx:160 msgid "Failed to update feeds" msgstr "無法更新動態" @@ -1910,7 +1944,7 @@ msgstr "無法更新設定" msgid "Feed" msgstr "動態" -#: src/components/FeedCard.tsx:90 +#: src/components/FeedCard.tsx:91 #: src/view/com/feeds/FeedSourceCard.tsx:251 msgid "Feed by {0}" msgstr "{0} 建立的動態源" @@ -1927,7 +1961,7 @@ msgstr "意見回饋" #: src/view/screens/Feeds.tsx:433 #: src/view/screens/Feeds.tsx:536 #: src/view/screens/Profile.tsx:197 -#: src/view/screens/Search/Search.tsx:373 +#: src/view/screens/Search/Search.tsx:375 #: src/view/shell/desktop/LeftNav.tsx:367 #: src/view/shell/Drawer.tsx:493 #: src/view/shell/Drawer.tsx:494 @@ -1938,7 +1972,7 @@ msgstr "動態源" msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." msgstr "動態源是一種自訂演算法,使用者只需掌握一點開發技巧即可輕鬆構建。更多資訊請<0/>。" -#: src/components/FeedCard.tsx:150 +#: src/components/FeedCard.tsx:157 msgid "Feeds updated!" msgstr "動態已更新!" @@ -1964,7 +1998,7 @@ msgstr "正在完成" msgid "Find accounts to follow" msgstr "尋找一些帳號來跟隨" -#: src/view/screens/Search/Search.tsx:437 +#: src/view/screens/Search/Search.tsx:439 msgid "Find posts and users on Bluesky" msgstr "在 Bluesky 上尋找貼文和用戶" @@ -2024,7 +2058,7 @@ msgstr "跟隨帳號" msgid "Follow Back" msgstr "回追蹤" -#: src/view/screens/Search/Explore.tsx:332 +#: src/view/screens/Search/Explore.tsx:333 msgid "Follow more accounts to get connected to your interests and build your network." msgstr "跟隨更多帳號以瞭解您的興趣,並建立您的社群網路。" @@ -2032,19 +2066,23 @@ msgstr "跟隨更多帳號以瞭解您的興趣,並建立您的社群網路。 msgid "Followed by {0}" msgstr "由 {0} 跟隨" -#: src/components/KnownFollowers.tsx:192 +#: src/components/KnownFollowers.tsx:223 msgid "Followed by <0>{0}" msgstr "已被你跟隨的 <0>{0} 跟隨" -#: src/components/KnownFollowers.tsx:181 +#: src/components/KnownFollowers.tsx:209 +msgid "Followed by <0>{0} and {1, plural, one {# other} other {# others}}" +msgstr "已被你跟隨的 <0>{0} 和{1, plural, one {其他 # 人跟隨} other {其他 # 人跟}}" + +#: src/components/KnownFollowers.tsx:196 msgid "Followed by <0>{0} and <1>{1}" msgstr "已被你跟隨的 <0>{0} 和 <1>{1} 跟隨" -#: src/components/KnownFollowers.tsx:168 +#: src/components/KnownFollowers.tsx:178 msgid "Followed by <0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}}" msgstr "已被你跟隨的 <0>{0}, <1>{1} 和{2, plural, one {其他 # 人跟隨} other {其他 # 人跟隨}}" -#: src/view/com/modals/Threadgate.tsx:99 +#: src/view/com/modals/Threadgate.tsx:101 msgid "Followed users" msgstr "已跟隨的用戶" @@ -2052,7 +2090,7 @@ msgstr "已跟隨的用戶" msgid "Followed users only" msgstr "僅限已跟隨的用戶" -#: src/view/com/notifications/FeedItem.tsx:173 +#: src/view/com/notifications/FeedItem.tsx:175 msgid "followed you" msgstr "已跟隨您" @@ -2099,7 +2137,7 @@ msgstr "「Following」動態源偏好" msgid "Following Feed Preferences" msgstr "「Following」動態源偏好" -#: src/screens/Profile/Header/Handle.tsx:24 +#: src/screens/Profile/Header/Handle.tsx:31 msgid "Follows you" msgstr "跟隨您" @@ -2140,7 +2178,7 @@ msgstr "頻繁發佈不當內容" msgid "From @{sanitizedAuthor}" msgstr "來自 @{sanitizedAuthor}" -#: src/view/com/posts/FeedItem.tsx:232 +#: src/view/com/posts/FeedItem.tsx:236 msgctxt "from-feed" msgid "From <0/>" msgstr "來自 <0/>" @@ -2158,6 +2196,10 @@ msgstr "開始" msgid "Get Started" msgstr "開始" +#: src/view/com/util/images/ImageHorzList.tsx:35 +msgid "GIF" +msgstr "GIF" + #: src/screens/Onboarding/StepProfile/index.tsx:225 msgid "Give your profile a face" msgstr "為您的個人檔案增添新顏" @@ -2263,35 +2305,35 @@ msgstr "這是您的應用程式專用密碼。" #: src/components/moderation/ContentHider.tsx:116 #: src/components/moderation/LabelPreference.tsx:134 -#: src/components/moderation/PostHider.tsx:121 +#: src/components/moderation/PostHider.tsx:122 #: src/lib/moderation/useLabelBehaviorDescription.ts:15 #: src/lib/moderation/useLabelBehaviorDescription.ts:20 #: src/lib/moderation/useLabelBehaviorDescription.ts:25 #: src/lib/moderation/useLabelBehaviorDescription.ts:30 -#: src/view/com/util/forms/PostDropdownBtn.tsx:435 +#: src/view/com/util/forms/PostDropdownBtn.tsx:442 msgid "Hide" msgstr "隱藏" -#: src/view/com/notifications/FeedItem.tsx:348 +#: src/view/com/notifications/FeedItem.tsx:350 msgctxt "action" msgid "Hide" msgstr "隱藏" -#: src/view/com/util/forms/PostDropdownBtn.tsx:380 -#: src/view/com/util/forms/PostDropdownBtn.tsx:382 +#: src/view/com/util/forms/PostDropdownBtn.tsx:387 +#: src/view/com/util/forms/PostDropdownBtn.tsx:389 msgid "Hide post" msgstr "隱藏貼文" #: src/components/moderation/ContentHider.tsx:68 -#: src/components/moderation/PostHider.tsx:78 +#: src/components/moderation/PostHider.tsx:79 msgid "Hide the content" msgstr "隱藏內容" -#: src/view/com/util/forms/PostDropdownBtn.tsx:432 +#: src/view/com/util/forms/PostDropdownBtn.tsx:439 msgid "Hide this post?" msgstr "隱藏這則貼文?" -#: src/view/com/notifications/FeedItem.tsx:339 +#: src/view/com/notifications/FeedItem.tsx:341 msgid "Hide user list" msgstr "隱藏用戶列表" @@ -2381,7 +2423,7 @@ msgstr "如果根據您所在國家的法律,您尚未成年,則您的父母 msgid "If you delete this list, you won't be able to recover it." msgstr "如果刪除這個列表,您將無法恢復它。" -#: src/view/com/util/forms/PostDropdownBtn.tsx:423 +#: src/view/com/util/forms/PostDropdownBtn.tsx:430 msgid "If you remove this post, you won't be able to recover it." msgstr "如果刪除這則貼文,您將無法恢復它。" @@ -2466,7 +2508,7 @@ msgstr "為您隆重介紹「私人訊息」" msgid "Invalid 2FA confirmation code." msgstr "無效的雙重驗證碼。" -#: src/view/com/post-thread/PostThreadItem.tsx:235 +#: src/view/com/post-thread/PostThreadItem.tsx:236 msgid "Invalid or unsupported post record" msgstr "無效或不支援的貼文紀錄" @@ -2482,7 +2524,7 @@ msgstr "邀請朋友" msgid "Invite code" msgstr "邀請碼" -#: src/screens/Signup/state.ts:272 +#: src/screens/Signup/state.ts:275 msgid "Invite code not accepted. Check that you input it correctly and try again." msgstr "邀請碼無效。請檢查您輸入的內容是否正確,然後重試。" @@ -2544,7 +2586,7 @@ msgid "Languages" msgstr "語言" #: src/screens/Hashtag.tsx:99 -#: src/view/screens/Search/Search.tsx:357 +#: src/view/screens/Search/Search.tsx:359 msgid "Latest" msgstr "最新" @@ -2557,7 +2599,7 @@ msgstr "瞭解詳情" msgid "Learn more about the moderation applied to this content." msgstr "詳細瞭解套用於此內容的內容管理。" -#: src/components/moderation/PostHider.tsx:99 +#: src/components/moderation/PostHider.tsx:100 #: src/components/moderation/ScreenHider.tsx:125 msgid "Learn more about this warning" msgstr "瞭解有關此警告的更多資訊" @@ -2633,11 +2675,11 @@ msgstr "按喜歡的用戶" msgid "Liked By" msgstr "按喜歡的用戶" -#: src/view/com/notifications/FeedItem.tsx:176 +#: src/view/com/notifications/FeedItem.tsx:178 msgid "liked your custom feed" msgstr "對您的自訂動態源表示喜歡" -#: src/view/com/notifications/FeedItem.tsx:168 +#: src/view/com/notifications/FeedItem.tsx:170 msgid "liked your post" msgstr "已喜歡您的貼文" @@ -2645,7 +2687,7 @@ msgstr "已喜歡您的貼文" msgid "Likes" msgstr "喜歡" -#: src/view/com/post-thread/PostThreadItem.tsx:196 +#: src/view/com/post-thread/PostThreadItem.tsx:197 msgid "Likes on this post" msgstr "這條貼文的喜歡數" @@ -2698,15 +2740,15 @@ msgstr "列表" msgid "Lists blocking this user:" msgstr "封鎖此用戶的列表:" -#: src/view/screens/Search/Explore.tsx:128 +#: src/view/screens/Search/Explore.tsx:130 msgid "Load more" msgstr "載入更多" -#: src/view/screens/Search/Explore.tsx:216 +#: src/view/screens/Search/Explore.tsx:218 msgid "Load more suggested feeds" msgstr "載入更多推薦動態" -#: src/view/screens/Search/Explore.tsx:214 +#: src/view/screens/Search/Explore.tsx:216 msgid "Load more suggested follows" msgstr "載入更多推薦跟隨者" @@ -2787,16 +2829,16 @@ msgstr "標記為已讀" msgid "Media" msgstr "媒體" -#: src/view/com/threadgate/WhoCanReply.tsx:139 +#: src/view/com/threadgate/WhoCanReply.tsx:183 msgid "mentioned users" msgstr "被提及的用戶" -#: src/view/com/modals/Threadgate.tsx:94 +#: src/view/com/modals/Threadgate.tsx:96 msgid "Mentioned users" msgstr "被提及的用戶" #: src/view/com/util/ViewHeader.tsx:91 -#: src/view/screens/Search/Search.tsx:681 +#: src/view/screens/Search/Search.tsx:683 msgid "Menu" msgstr "選單" @@ -2896,7 +2938,7 @@ msgstr "內容管理工具" msgid "Moderator has chosen to set a general warning on the content." msgstr "內容管理者已將此內容標記為普通警告。" -#: src/view/com/post-thread/PostThreadItem.tsx:566 +#: src/view/com/post-thread/PostThreadItem.tsx:571 msgid "More" msgstr "更多" @@ -2962,13 +3004,13 @@ msgstr "在貼文內容和話題標籤中隱藏該文字" msgid "Mute this word in tags only" msgstr "僅在話題標籤中隱藏該文字" -#: src/view/com/util/forms/PostDropdownBtn.tsx:355 -#: src/view/com/util/forms/PostDropdownBtn.tsx:361 +#: src/view/com/util/forms/PostDropdownBtn.tsx:362 +#: src/view/com/util/forms/PostDropdownBtn.tsx:368 msgid "Mute thread" msgstr "靜音討論串" -#: src/view/com/util/forms/PostDropdownBtn.tsx:371 -#: src/view/com/util/forms/PostDropdownBtn.tsx:373 +#: src/view/com/util/forms/PostDropdownBtn.tsx:378 +#: src/view/com/util/forms/PostDropdownBtn.tsx:380 msgid "Mute words & tags" msgstr "靜音文字和標籤" @@ -3114,6 +3156,10 @@ msgctxt "action" msgid "New Post" msgstr "新貼文" +#: src/components/NewskieDialog.tsx:68 +msgid "New user info dialog" +msgstr "新用戶資訊對話框" + #: src/view/com/modals/CreateOrEditList.tsx:236 msgid "New User List" msgstr "新的用戶列表" @@ -3216,7 +3262,7 @@ msgstr "未找到「{query}」的結果" #: src/view/com/modals/ListAddRemoveUsers.tsx:127 #: src/view/screens/Search/Search.tsx:233 #: src/view/screens/Search/Search.tsx:272 -#: src/view/screens/Search/Search.tsx:316 +#: src/view/screens/Search/Search.tsx:318 msgid "No results found for {query}" msgstr "未找到 {query} 的結果" @@ -3230,7 +3276,7 @@ msgstr "未找到「{search}」的搜尋結果。" msgid "No thanks" msgstr "不,謝謝" -#: src/view/com/modals/Threadgate.tsx:83 +#: src/view/com/modals/Threadgate.tsx:85 msgid "Nobody" msgstr "沒有人" @@ -3258,7 +3304,7 @@ msgid "Not right now" msgstr "暫時不需要" #: src/view/com/profile/ProfileMenu.tsx:373 -#: src/view/com/util/forms/PostDropdownBtn.tsx:449 +#: src/view/com/util/forms/PostDropdownBtn.tsx:456 #: src/view/com/util/post-ctrls/PostCtrls.tsx:310 msgid "Note about sharing" msgstr "關於分享的注意事項" @@ -3289,6 +3335,10 @@ msgstr "通知音效" msgid "Notifications" msgstr "通知" +#: src/lib/hooks/useTimeAgo.ts:51 +msgid "now" +msgstr "現在" + #: src/components/dms/MessageItem.tsx:175 msgid "Now" msgstr "現在" @@ -3327,6 +3377,10 @@ msgstr "好的" msgid "Oldest replies first" msgstr "最舊的回覆優先" +#: src/lib/hooks/useTimeAgo.ts:81 +msgid "on {str}" +msgstr "在 {str}" + #: src/view/screens/Settings/index.tsx:256 msgid "Onboarding reset" msgstr "重新開始引導流程" @@ -3339,7 +3393,7 @@ msgstr "至少有一張圖片缺失了替代文字。" msgid "Only .jpg and .png files are supported" msgstr "僅支援 .jpg 或 .png 格式的圖片" -#: src/view/com/threadgate/WhoCanReply.tsx:100 +#: src/view/com/threadgate/WhoCanReply.tsx:133 msgid "Only {0} can reply." msgstr "只有{0}可以回覆。" @@ -3399,7 +3453,7 @@ msgstr "開啟靜音文字和標籤設定" msgid "Open navigation" msgstr "開啟導覽" -#: src/view/com/util/forms/PostDropdownBtn.tsx:240 +#: src/view/com/util/forms/PostDropdownBtn.tsx:247 msgid "Open post options menu" msgstr "開啟貼文選項選單" @@ -3420,7 +3474,7 @@ msgstr "開啟 {numItems} 個選項" msgid "Opens accessibility settings" msgstr "開啟無障礙設定" -#: src/view/screens/Log.tsx:54 +#: src/view/screens/Log.tsx:58 msgid "Opens additional details for a debug entry" msgstr "開啟除錯項目的額外詳細資訊" @@ -3531,8 +3585,8 @@ msgstr "開啟系統日誌頁面" msgid "Opens the threads preferences" msgstr "開啟討論串偏好" -#: src/view/com/notifications/FeedItem.tsx:427 -#: src/view/com/util/UserAvatar.tsx:409 +#: src/view/com/notifications/FeedItem.tsx:429 +#: src/view/com/util/UserAvatar.tsx:422 msgid "Opens this profile" msgstr "開啟這個個人檔案" @@ -3545,7 +3599,7 @@ msgstr "{0} 選項,共 {numItems} 個" msgid "Optionally provide additional information below:" msgstr "在以下提供額外訊息(可選):" -#: src/view/com/modals/Threadgate.tsx:90 +#: src/view/com/modals/Threadgate.tsx:92 msgid "Or combine these options:" msgstr "或者組合這些選項:" @@ -3605,7 +3659,7 @@ msgstr "密碼已更新!" msgid "Pause" msgstr "暫停" -#: src/view/screens/Search/Search.tsx:367 +#: src/view/screens/Search/Search.tsx:369 msgid "People" msgstr "用戶" @@ -3742,7 +3796,7 @@ msgctxt "action" msgid "Post" msgstr "發佈" -#: src/view/com/post-thread/PostThread.tsx:430 +#: src/view/com/post-thread/PostThread.tsx:434 msgctxt "description" msgid "Post" msgstr "貼文" @@ -3757,7 +3811,7 @@ msgstr "{0} 的貼文" msgid "Post by @{0}" msgstr "@{0} 的貼文" -#: src/view/com/util/forms/PostDropdownBtn.tsx:129 +#: src/view/com/util/forms/PostDropdownBtn.tsx:132 msgid "Post deleted" msgstr "貼文已刪除" @@ -3823,7 +3877,7 @@ msgstr "按下以更改託管服務供應商" msgid "Press to retry" msgstr "按下以重試" -#: src/components/KnownFollowers.tsx:108 +#: src/components/KnownFollowers.tsx:116 msgid "Press to view followers of this account that you also follow" msgstr "按下以查看哪些您認識的人跟隨了此帳號" @@ -3924,7 +3978,7 @@ msgstr "重新啟用您的帳號" msgid "Reason:" msgstr "原因:" -#: src/view/screens/Search/Search.tsx:937 +#: src/view/screens/Search/Search.tsx:933 msgid "Recent Searches" msgstr "最近的搜尋結果" @@ -3937,7 +3991,7 @@ msgid "Reload conversations" msgstr "重新載入對話" #: src/components/dialogs/MutedWords.tsx:286 -#: src/components/FeedCard.tsx:193 +#: src/components/FeedCard.tsx:200 #: src/view/com/feeds/FeedSourceCard.tsx:317 #: src/view/com/modals/ListAddRemoveUsers.tsx:268 #: src/view/com/modals/SelfLabel.tsx:84 @@ -3950,7 +4004,7 @@ msgstr "刪除" msgid "Remove account" msgstr "刪除帳號" -#: src/view/com/util/UserAvatar.tsx:371 +#: src/view/com/util/UserAvatar.tsx:384 msgid "Remove Avatar" msgstr "刪除頭像" @@ -3980,7 +4034,7 @@ msgstr "刪除動態源?" msgid "Remove from my feeds" msgstr "從我的動態源中刪除" -#: src/components/FeedCard.tsx:188 +#: src/components/FeedCard.tsx:195 #: src/view/com/feeds/FeedSourceCard.tsx:312 msgid "Remove from my feeds?" msgstr "從我的動態源中刪除?" @@ -3997,11 +4051,11 @@ msgstr "刪除圖片預覽" msgid "Remove mute word from your list" msgstr "從您的列表中刪除靜音文字" -#: src/view/screens/Search/Search.tsx:978 +#: src/view/screens/Search/Search.tsx:974 msgid "Remove profile" msgstr "刪除個人檔案" -#: src/view/screens/Search/Search.tsx:980 +#: src/view/screens/Search/Search.tsx:976 msgid "Remove profile from search history" msgstr "刪除搜尋紀錄中的個人檔案" @@ -4050,9 +4104,9 @@ msgstr "用「Discover」動態源取代" msgid "Replies" msgstr "回覆" -#: src/view/com/threadgate/WhoCanReply.tsx:98 -msgid "Replies to this thread are disabled" -msgstr "對此討論串的回覆已停用" +#: src/view/com/threadgate/WhoCanReply.tsx:131 +msgid "Replies to this thread are disabled." +msgstr "對此討論串的回覆已停用。" #: src/view/com/composer/Composer.tsx:477 msgctxt "action" @@ -4064,11 +4118,16 @@ msgid "Reply Filters" msgstr "回覆過濾器" #: src/view/com/post/Post.tsx:190 -#: src/view/com/posts/FeedItem.tsx:427 +#: src/view/com/posts/FeedItem.tsx:439 msgctxt "description" msgid "Reply to <0><1/>" msgstr "對 <0><1/> 回覆" +#: src/view/com/posts/FeedItem.tsx:437 +msgctxt "description" +msgid "Reply to a blocked post" +msgstr "對已被封鎖的貼文回覆" + #: src/components/dms/MessageMenu.tsx:132 #: src/components/dms/MessagesListBlockedFooter.tsx:77 #: src/components/dms/MessagesListBlockedFooter.tsx:84 @@ -4103,8 +4162,8 @@ msgstr "檢舉列表" msgid "Report message" msgstr "檢舉訊息" -#: src/view/com/util/forms/PostDropdownBtn.tsx:397 -#: src/view/com/util/forms/PostDropdownBtn.tsx:399 +#: src/view/com/util/forms/PostDropdownBtn.tsx:404 +#: src/view/com/util/forms/PostDropdownBtn.tsx:406 msgid "Report post" msgstr "檢舉貼文" @@ -4156,19 +4215,19 @@ msgstr "轉貼或引用貼文" msgid "Reposted By" msgstr "轉貼" -#: src/view/com/posts/FeedItem.tsx:250 +#: src/view/com/posts/FeedItem.tsx:254 msgid "Reposted by {0}" msgstr "由 {0} 轉貼" -#: src/view/com/posts/FeedItem.tsx:265 +#: src/view/com/posts/FeedItem.tsx:269 msgid "Reposted by <0><1/>" msgstr "由 <0><1/> 轉貼" -#: src/view/com/notifications/FeedItem.tsx:170 +#: src/view/com/notifications/FeedItem.tsx:172 msgid "reposted your post" msgstr "轉貼您的貼文" -#: src/view/com/post-thread/PostThreadItem.tsx:201 +#: src/view/com/post-thread/PostThreadItem.tsx:202 msgid "Reposts of this post" msgstr "轉貼這則貼文" @@ -4332,6 +4391,7 @@ msgid "Saves image crop settings" msgstr "儲存圖片裁剪設定" #: src/components/dms/ChatEmptyPill.tsx:33 +#: src/components/NewskieDialog.tsx:72 msgid "Say hello!" msgstr "說句「你好!👋」" @@ -4349,9 +4409,9 @@ msgstr "滾動到頂部" #: src/view/com/modals/ListAddRemoveUsers.tsx:75 #: src/view/com/util/forms/SearchInput.tsx:67 #: src/view/com/util/forms/SearchInput.tsx:79 -#: src/view/screens/Search/Search.tsx:419 -#: src/view/screens/Search/Search.tsx:789 -#: src/view/screens/Search/Search.tsx:817 +#: src/view/screens/Search/Search.tsx:421 +#: src/view/screens/Search/Search.tsx:791 +#: src/view/screens/Search/Search.tsx:813 #: src/view/shell/bottom-bar/BottomBar.tsx:179 #: src/view/shell/desktop/LeftNav.tsx:343 #: src/view/shell/desktop/Search.tsx:194 @@ -4365,7 +4425,7 @@ msgstr "搜尋" msgid "Search for \"{query}\"" msgstr "搜尋「{query}」" -#: src/view/screens/Search/Search.tsx:873 +#: src/view/screens/Search/Search.tsx:869 msgid "Search for \"{searchText}\"" msgstr "搜尋「{searchText}」" @@ -4546,8 +4606,8 @@ msgstr "將檢舉提交至 {0}" msgid "Send verification email" msgstr "發送驗證電子郵件" -#: src/view/com/util/forms/PostDropdownBtn.tsx:289 -#: src/view/com/util/forms/PostDropdownBtn.tsx:292 +#: src/view/com/util/forms/PostDropdownBtn.tsx:296 +#: src/view/com/util/forms/PostDropdownBtn.tsx:299 msgid "Send via direct message" msgstr "透過私人訊息發送" @@ -4654,8 +4714,8 @@ msgstr "分享" #: src/view/com/profile/ProfileMenu.tsx:220 #: src/view/com/profile/ProfileMenu.tsx:229 -#: src/view/com/util/forms/PostDropdownBtn.tsx:300 -#: src/view/com/util/forms/PostDropdownBtn.tsx:309 +#: src/view/com/util/forms/PostDropdownBtn.tsx:307 +#: src/view/com/util/forms/PostDropdownBtn.tsx:316 #: src/view/com/util/post-ctrls/PostCtrls.tsx:299 #: src/view/screens/ProfileList.tsx:428 msgid "Share" @@ -4670,7 +4730,7 @@ msgid "Share a fun fact!" msgstr "分享一個趣聞!📰" #: src/view/com/profile/ProfileMenu.tsx:378 -#: src/view/com/util/forms/PostDropdownBtn.tsx:454 +#: src/view/com/util/forms/PostDropdownBtn.tsx:461 #: src/view/com/util/post-ctrls/PostCtrls.tsx:315 msgid "Share anyway" msgstr "仍然分享" @@ -4695,7 +4755,7 @@ msgstr "分享網站的連結" #: src/components/moderation/ContentHider.tsx:116 #: src/components/moderation/LabelPreference.tsx:136 -#: src/components/moderation/PostHider.tsx:121 +#: src/components/moderation/PostHider.tsx:122 #: src/view/screens/Settings/index.tsx:381 msgid "Show" msgstr "顯示" @@ -4726,19 +4786,19 @@ msgstr "顯示類似於 {0} 的跟隨者" msgid "Show hidden replies" msgstr "顯示隱藏回覆" -#: src/view/com/util/forms/PostDropdownBtn.tsx:339 -#: src/view/com/util/forms/PostDropdownBtn.tsx:341 +#: src/view/com/util/forms/PostDropdownBtn.tsx:346 +#: src/view/com/util/forms/PostDropdownBtn.tsx:348 msgid "Show less like this" msgstr "減少顯示此類內容" -#: src/view/com/post-thread/PostThreadItem.tsx:532 +#: src/view/com/post-thread/PostThreadItem.tsx:537 #: src/view/com/post/Post.tsx:227 -#: src/view/com/posts/FeedItem.tsx:392 +#: src/view/com/posts/FeedItem.tsx:396 msgid "Show More" msgstr "顯示更多" -#: src/view/com/util/forms/PostDropdownBtn.tsx:331 -#: src/view/com/util/forms/PostDropdownBtn.tsx:333 +#: src/view/com/util/forms/PostDropdownBtn.tsx:338 +#: src/view/com/util/forms/PostDropdownBtn.tsx:340 msgid "Show more like this" msgstr "顯示更多此類內容" @@ -4767,7 +4827,7 @@ msgid "Show Reposts" msgstr "顯示轉貼貼文" #: src/components/moderation/ContentHider.tsx:69 -#: src/components/moderation/PostHider.tsx:78 +#: src/components/moderation/PostHider.tsx:79 msgid "Show the content" msgstr "顯示內容" @@ -4886,7 +4946,7 @@ msgstr "發生了一些問題,請重試" msgid "Something went wrong, please try again." msgstr "發生了一些問題,請重試。" -#: src/App.native.tsx:85 +#: src/App.native.tsx:92 #: src/App.web.tsx:74 msgid "Sorry! Your session expired. Please log in again." msgstr "抱歉!您的登入會話已過期。請重新登入。" @@ -4976,7 +5036,7 @@ msgstr "訂閱這個標記者" msgid "Subscribe to this list" msgstr "訂閱這個列表" -#: src/view/screens/Search/Explore.tsx:330 +#: src/view/screens/Search/Explore.tsx:331 msgid "Suggested accounts" msgstr "推薦的帳號" @@ -5334,12 +5394,12 @@ msgstr "此名稱已被使用" msgid "This post has been deleted." msgstr "這則貼文已被刪除。" -#: src/view/com/util/forms/PostDropdownBtn.tsx:451 +#: src/view/com/util/forms/PostDropdownBtn.tsx:458 #: src/view/com/util/post-ctrls/PostCtrls.tsx:312 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:433 +#: src/view/com/util/forms/PostDropdownBtn.tsx:440 msgid "This post will be hidden from feeds." msgstr "這則貼文將從動態隱藏。" @@ -5380,6 +5440,10 @@ msgstr "此用戶包含在您已封鎖的 <0>{0} 列表中。" msgid "This user is included in the <0>{0} list which you have muted." msgstr "此用戶包含在您已靜音的 <0>{0} 列表中。" +#: src/components/NewskieDialog.tsx:50 +msgid "This user is new here. Press for more info about when they joined." +msgstr "該用戶是新來帳號,請按此了解更多有關他們何時加入的資訊。" + #: src/view/com/profile/ProfileFollows.tsx:87 msgid "This user isn't following anyone." msgstr "此用戶未跟隨任何人。" @@ -5430,7 +5494,7 @@ msgid "Toggle to enable or disable adult content" msgstr "切換以啟用或停用成人內容" #: src/screens/Hashtag.tsx:88 -#: src/view/screens/Search/Search.tsx:347 +#: src/view/screens/Search/Search.tsx:349 msgid "Top" msgstr "熱門" @@ -5440,10 +5504,10 @@ msgstr "轉換" #: src/components/dms/MessageMenu.tsx:103 #: src/components/dms/MessageMenu.tsx:105 -#: src/view/com/post-thread/PostThreadItem.tsx:674 -#: src/view/com/post-thread/PostThreadItem.tsx:676 -#: src/view/com/util/forms/PostDropdownBtn.tsx:270 -#: src/view/com/util/forms/PostDropdownBtn.tsx:272 +#: src/view/com/post-thread/PostThreadItem.tsx:681 +#: src/view/com/post-thread/PostThreadItem.tsx:683 +#: src/view/com/util/forms/PostDropdownBtn.tsx:277 +#: src/view/com/util/forms/PostDropdownBtn.tsx:279 msgid "Translate" msgstr "翻譯" @@ -5562,8 +5626,8 @@ msgstr "取消對所有 {displayTag} 貼文的靜音" msgid "Unmute conversation" msgstr "取消靜音對話" -#: src/view/com/util/forms/PostDropdownBtn.tsx:355 -#: src/view/com/util/forms/PostDropdownBtn.tsx:360 +#: src/view/com/util/forms/PostDropdownBtn.tsx:362 +#: src/view/com/util/forms/PostDropdownBtn.tsx:367 msgid "Unmute thread" msgstr "取消靜音討論串" @@ -5617,20 +5681,20 @@ msgstr "或是上傳圖片" msgid "Upload a text file to:" msgstr "上傳文字檔案至:" -#: src/view/com/util/UserAvatar.tsx:339 -#: src/view/com/util/UserAvatar.tsx:342 +#: src/view/com/util/UserAvatar.tsx:352 +#: src/view/com/util/UserAvatar.tsx:355 #: src/view/com/util/UserBanner.tsx:123 #: src/view/com/util/UserBanner.tsx:126 msgid "Upload from Camera" msgstr "從相機上傳" -#: src/view/com/util/UserAvatar.tsx:356 +#: src/view/com/util/UserAvatar.tsx:369 #: src/view/com/util/UserBanner.tsx:140 msgid "Upload from Files" msgstr "從檔案上傳" -#: src/view/com/util/UserAvatar.tsx:350 -#: src/view/com/util/UserAvatar.tsx:354 +#: src/view/com/util/UserAvatar.tsx:363 +#: src/view/com/util/UserAvatar.tsx:367 #: src/view/com/util/UserBanner.tsx:134 #: src/view/com/util/UserBanner.tsx:138 msgid "Upload from Library" @@ -5738,7 +5802,7 @@ msgstr "帳號代碼或電子郵件地址" msgid "Users" msgstr "用戶" -#: src/view/com/threadgate/WhoCanReply.tsx:143 +#: src/view/com/threadgate/WhoCanReply.tsx:187 msgid "users followed by <0/>" msgstr "被 <0/> 跟隨的用戶" @@ -5749,7 +5813,7 @@ msgstr "被 <0/> 跟隨的用戶" msgid "Users I follow" msgstr "我跟隨的用戶" -#: src/view/com/modals/Threadgate.tsx:107 +#: src/view/com/modals/Threadgate.tsx:109 msgid "Users in \"{0}\"" msgstr "「{0}」中的用戶" @@ -5802,7 +5866,7 @@ msgstr "電子遊戲" msgid "View {0}'s avatar" msgstr "查看 {0} 的頭像" -#: src/view/com/notifications/FeedItem.tsx:213 +#: src/view/com/notifications/FeedItem.tsx:215 msgid "View {0}'s profile" msgstr "查看 {0} 的個人檔案" @@ -5810,7 +5874,7 @@ msgstr "查看 {0} 的個人檔案" msgid "View blocked user's profile" msgstr "查看已封鎖用戶的個人檔案" -#: src/view/screens/Log.tsx:52 +#: src/view/screens/Log.tsx:56 msgid "View debug entry" msgstr "查看偵錯項目" @@ -5822,7 +5886,7 @@ msgstr "查看詳細資訊" msgid "View details for reporting a copyright violation" msgstr "查看詳細資訊以檢舉侵犯版權" -#: src/view/com/posts/FeedSlice.tsx:120 +#: src/view/com/posts/FeedSlice.tsx:124 msgid "View full thread" msgstr "查看整個討論串" @@ -5948,8 +6012,8 @@ msgid "We're sorry! We can't find the page you were looking for." msgstr "很抱歉!我們找不到您正在尋找的頁面。" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:330 -msgid "We're sorry! You can only subscribe to ten labelers, and you've reached your limit of ten." -msgstr "抱歉!您只能訂閱十個標記者,您已達到十個的限制。" +msgid "We're sorry! You can only subscribe to twenty labelers, and you've reached your limit of twenty." +msgstr "抱歉!您只能訂閱二十個標記者,您已達到二十個的限制。" #: src/screens/Deactivated.tsx:128 msgid "Welcome back!" @@ -5978,7 +6042,7 @@ msgstr "您想在演算法動態源中看到哪些語言?" msgid "Who can message you?" msgstr "誰可以傳送訊息給您?" -#: src/view/com/modals/Threadgate.tsx:67 +#: src/view/com/modals/Threadgate.tsx:69 msgid "Who can reply" msgstr "誰可以回覆" @@ -6201,11 +6265,11 @@ msgstr "您必須選擇至少一個標記者來提交檢舉" msgid "You previously deactivated @{0}." msgstr "您之前停用了 @{0}。" -#: src/view/com/util/forms/PostDropdownBtn.tsx:168 +#: src/view/com/util/forms/PostDropdownBtn.tsx:174 msgid "You will no longer receive notifications for this thread" msgstr "您將不再收到這條討論串的通知" -#: src/view/com/util/forms/PostDropdownBtn.tsx:171 +#: src/view/com/util/forms/PostDropdownBtn.tsx:170 msgid "You will now receive notifications for this thread" msgstr "您將收到這條討論串的通知" From 0f931933a7b7b72505fbab6e1f25860f7a84e59c Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 19 Jun 2024 22:32:44 +0100 Subject: [PATCH 062/127] Option for large alt badges (#4571) * add pref for large alt badge * add to settings * do the large badge bit * Tweak wording --------- Co-authored-by: Dan Abramov --- src/state/persisted/schema.ts | 2 + src/state/preferences/alt-text-required.tsx | 1 + src/state/preferences/hidden-posts.tsx | 1 + src/state/preferences/index.tsx | 25 ++++++----- src/state/preferences/large-alt-badge.tsx | 49 +++++++++++++++++++++ src/view/com/util/images/Gallery.tsx | 22 +++++---- src/view/com/util/post-embeds/GifEmbed.tsx | 6 ++- src/view/com/util/post-embeds/index.tsx | 15 +++---- src/view/screens/AccessibilitySettings.tsx | 35 ++++++++++----- 9 files changed, 113 insertions(+), 43 deletions(-) create mode 100644 src/state/preferences/large-alt-badge.tsx diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 9d5b17d354..c942828f2a 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -60,6 +60,7 @@ export const schema = z.object({ appLanguage: z.string(), }), requireAltTextEnabled: z.boolean(), // should move to server + largeAltBadgeEnabled: z.boolean().optional(), externalEmbeds: z .object({ giphy: z.enum(externalEmbedOptions).optional(), @@ -112,6 +113,7 @@ export const defaults: Schema = { appLanguage: deviceLocales[0] || 'en', }, requireAltTextEnabled: false, + largeAltBadgeEnabled: false, externalEmbeds: {}, mutedThreads: [], invites: { diff --git a/src/state/preferences/alt-text-required.tsx b/src/state/preferences/alt-text-required.tsx index 81de9e0060..642e790fbc 100644 --- a/src/state/preferences/alt-text-required.tsx +++ b/src/state/preferences/alt-text-required.tsx @@ -1,4 +1,5 @@ import React from 'react' + import * as persisted from '#/state/persisted' type StateContext = persisted.Schema['requireAltTextEnabled'] diff --git a/src/state/preferences/hidden-posts.tsx b/src/state/preferences/hidden-posts.tsx index 11119ce758..2c6a373e15 100644 --- a/src/state/preferences/hidden-posts.tsx +++ b/src/state/preferences/hidden-posts.tsx @@ -1,4 +1,5 @@ import React from 'react' + import * as persisted from '#/state/persisted' type SetStateCb = ( diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index 70c8efc805..e1a35f193c 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -8,6 +8,7 @@ import {Provider as HiddenPostsProvider} from './hidden-posts' import {Provider as InAppBrowserProvider} from './in-app-browser' import {Provider as KawaiiProvider} from './kawaii' import {Provider as LanguagesProvider} from './languages' +import {Provider as LargeAltBadgeProvider} from './large-alt-badge' export { useRequireAltTextEnabled, @@ -27,17 +28,19 @@ export function Provider({children}: React.PropsWithChildren<{}>) { return ( - - - - - - {children} - - - - - + + + + + + + {children} + + + + + + ) diff --git a/src/state/preferences/large-alt-badge.tsx b/src/state/preferences/large-alt-badge.tsx new file mode 100644 index 0000000000..b3d597c5cb --- /dev/null +++ b/src/state/preferences/large-alt-badge.tsx @@ -0,0 +1,49 @@ +import React from 'react' + +import * as persisted from '#/state/persisted' + +type StateContext = persisted.Schema['largeAltBadgeEnabled'] +type SetContext = (v: persisted.Schema['largeAltBadgeEnabled']) => void + +const stateContext = React.createContext( + persisted.defaults.largeAltBadgeEnabled, +) +const setContext = React.createContext( + (_: persisted.Schema['largeAltBadgeEnabled']) => {}, +) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [state, setState] = React.useState( + persisted.get('largeAltBadgeEnabled'), + ) + + const setStateWrapped = React.useCallback( + (largeAltBadgeEnabled: persisted.Schema['largeAltBadgeEnabled']) => { + setState(largeAltBadgeEnabled) + persisted.write('largeAltBadgeEnabled', largeAltBadgeEnabled) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate(() => { + setState(persisted.get('largeAltBadgeEnabled')) + }) + }, [setStateWrapped]) + + return ( + + + {children} + + + ) +} + +export function useLargeAltBadgeEnabled() { + return React.useContext(stateContext) +} + +export function useSetLargeAltBadgeEnabled() { + return React.useContext(setContext) +} diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx index 8d23d258f5..9bbb2ac100 100644 --- a/src/view/com/util/images/Gallery.tsx +++ b/src/view/com/util/images/Gallery.tsx @@ -5,7 +5,9 @@ import {AppBskyEmbedImages} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {isWeb} from 'platform/detection' +import {isWeb} from '#/platform/detection' +import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' +import {atoms as a} from '#/alf' type EventFunction = (index: number) => void @@ -27,20 +29,21 @@ export const GalleryItem: FC = ({ onLongPress, }) => { const {_} = useLingui() + const largeAltBadge = useLargeAltBadgeEnabled() const image = images[index] return ( - + onPress(index) : undefined} onPressIn={onPressIn ? () => onPressIn(index) : undefined} onLongPress={onLongPress ? () => onLongPress(index) : undefined} - style={styles.fullWidth} + style={a.flex_1} accessibilityRole="button" accessibilityLabel={image.alt || _(msg`Image`)} accessibilityHint=""> = ({ {image.alt === '' ? null : ( - + ALT @@ -59,13 +64,6 @@ export const GalleryItem: FC = ({ } const styles = StyleSheet.create({ - fullWidth: { - flex: 1, - }, - image: { - flex: 1, - borderRadius: 4, - }, altContainer: { backgroundColor: 'rgba(0, 0, 0, 0.75)', borderRadius: 6, diff --git a/src/view/com/util/post-embeds/GifEmbed.tsx b/src/view/com/util/post-embeds/GifEmbed.tsx index f2e2a8b0e9..1558b75c62 100644 --- a/src/view/com/util/post-embeds/GifEmbed.tsx +++ b/src/view/com/util/post-embeds/GifEmbed.tsx @@ -8,6 +8,7 @@ import {useLingui} from '@lingui/react' import {HITSLOP_20} from '#/lib/constants' import {parseAltFromGIFDescription} from '#/lib/gif-alt-text' import {isWeb} from '#/platform/detection' +import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' import {EmbedPlayerParams} from 'lib/strings/embed-player' import {useAutoplayDisabled} from 'state/preferences' import {atoms as a, useTheme} from '#/alf' @@ -157,6 +158,7 @@ export function GifEmbed({ function AltText({text}: {text: string}) { const control = Prompt.usePromptControl() + const largeAltBadge = useLargeAltBadgeEnabled() const {_} = useLingui() return ( @@ -169,7 +171,9 @@ function AltText({text}: {text: string}) { hitSlop={HITSLOP_20} onPress={control.open} style={styles.altContainer}> - + ALT diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index a13fffc370..be34a2869e 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -21,6 +21,7 @@ import { import {ImagesLightbox, useLightboxControls} from '#/state/lightbox' import {usePalette} from 'lib/hooks/usePalette' import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' +import {atoms as a} from '#/alf' import {ContentHider} from '../../../../components/moderation/ContentHider' import {AutoSizedImage} from '../images/AutoSizedImage' import {ImageLayoutGrid} from '../images/ImageLayoutGrid' @@ -28,6 +29,7 @@ import {ExternalLinkEmbed} from './ExternalLinkEmbed' import {ListEmbed} from './ListEmbed' import {MaybeQuoteEmbed} from './QuoteEmbed' import hairlineWidth = StyleSheet.hairlineWidth +import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' type Embed = | AppBskyEmbedRecord.View @@ -51,6 +53,7 @@ export function PostEmbeds({ }) { const pal = usePalette('default') const {openLightbox} = useLightboxControls() + const largeAltBadge = useLargeAltBadgeEnabled() // quote post with media // = @@ -130,10 +133,12 @@ export function PostEmbeds({ dimensionsHint={aspectRatio} onPress={() => _openLightbox(0)} onPressIn={() => onPressIn(0)} - style={[styles.singleImage]}> + style={a.rounded_sm}> {alt === '' ? null : ( - + ALT @@ -151,9 +156,6 @@ export function PostEmbeds({ images={embed.images} onPress={_openLightbox} onPressIn={onPressIn} - style={ - embed.images.length === 1 ? [styles.singleImage] : undefined - } /> @@ -179,9 +181,6 @@ const styles = StyleSheet.create({ imagesContainer: { marginTop: 8, }, - singleImage: { - borderRadius: 8, - }, altContainer: { backgroundColor: 'rgba(0, 0, 0, 0.75)', borderRadius: 6, diff --git a/src/view/screens/AccessibilitySettings.tsx b/src/view/screens/AccessibilitySettings.tsx index ac0d985f10..9ac9793367 100644 --- a/src/view/screens/AccessibilitySettings.tsx +++ b/src/view/screens/AccessibilitySettings.tsx @@ -4,13 +4,12 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' +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 {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, @@ -18,11 +17,16 @@ import { 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' +} from '#/state/preferences' +import { + useLargeAltBadgeEnabled, + useSetLargeAltBadgeEnabled, +} from '#/state/preferences/large-alt-badge' +import {useSetMinimalShellMode} from '#/state/shell' +import {ToggleButton} from '#/view/com/util/forms/ToggleButton' +import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader' +import {Text} from '#/view/com/util/text/Text' +import {ScrollView} from '#/view/com/util/Views' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -41,6 +45,8 @@ export function AccessibilitySettingsScreen({}: Props) { const setAutoplayDisabled = useSetAutoplayDisabled() const hapticsDisabled = useHapticsDisabled() const setHapticsDisabled = useSetHapticsDisabled() + const largeAltBadgeEnabled = useLargeAltBadgeEnabled() + const setLargeAltBadgeEnabled = useSetLargeAltBadgeEnabled() useFocusEffect( React.useCallback(() => { @@ -84,6 +90,13 @@ export function AccessibilitySettingsScreen({}: Props) { isSelected={requireAltTextEnabled} onPress={() => setRequireAltTextEnabled(!requireAltTextEnabled)} /> + setLargeAltBadgeEnabled(!largeAltBadgeEnabled)} + /> Media From 73fc0094dd5e7056d2612965f81648b9ffe423d0 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 19 Jun 2024 22:45:08 +0100 Subject: [PATCH 063/127] Update HomeHeaderLayoutMobile.tsx (#4572) --- src/view/com/home/HomeHeaderLayoutMobile.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view/com/home/HomeHeaderLayoutMobile.tsx b/src/view/com/home/HomeHeaderLayoutMobile.tsx index 895baa9a4d..8cf0452cec 100644 --- a/src/view/com/home/HomeHeaderLayoutMobile.tsx +++ b/src/view/com/home/HomeHeaderLayoutMobile.tsx @@ -120,6 +120,7 @@ const styles = StyleSheet.create({ paddingHorizontal: 16, paddingVertical: 5, width: '100%', + minHeight: 46, }, title: { fontSize: 21, From 5c31859f7bb753b07752e2b8faf2248561c46c54 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 19 Jun 2024 23:42:12 +0100 Subject: [PATCH 064/127] fix for autofill covering border (#4573) --- src/components/forms/TextField.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx index 73a660ea6c..f7a827b493 100644 --- a/src/components/forms/TextField.tsx +++ b/src/components/forms/TextField.tsx @@ -196,6 +196,13 @@ export function createInput(Component: typeof TextInput) { textAlignVertical: rest.multiline ? 'top' : undefined, minHeight: rest.multiline ? 80 : undefined, }, + // fix for autofill styles covering border + web({ + paddingTop: 12, + paddingBottom: 12, + marginTop: 2, + marginBottom: 2, + }), android({ paddingBottom: 16, }), From 89d99a87019aced9008f33e37e55bd98ec8a7084 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 19 Jun 2024 16:20:43 -0700 Subject: [PATCH 065/127] use 1000x1000 for image height in avatar cropper (#4453) --- src/screens/Onboarding/StepProfile/index.tsx | 4 ++-- src/view/com/util/UserAvatar.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/screens/Onboarding/StepProfile/index.tsx b/src/screens/Onboarding/StepProfile/index.tsx index 3556bba7a0..5304aa5031 100644 --- a/src/screens/Onboarding/StepProfile/index.tsx +++ b/src/screens/Onboarding/StepProfile/index.tsx @@ -181,8 +181,8 @@ export function StepProfile() { image = await openCropper({ mediaType: 'photo', cropperCircleOverlay: true, - height: image.height, - width: image.width, + height: 1000, + width: 1000, path: image.path, }) } diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index c212ea4c02..e9aa625806 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -303,8 +303,8 @@ let EditableUserAvatar = ({ const croppedImage = await openCropper({ mediaType: 'photo', cropperCircleOverlay: true, - height: item.height, - width: item.width, + height: 1000, + width: 1000, path: item.path, }) From 7d8fca56dc25b31b592692ad385105be42285c72 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 19 Jun 2024 18:47:43 -0500 Subject: [PATCH 066/127] Convert button to use forwardRef (#4576) --- src/components/Button.tsx | 667 ++++++++++++++++++++------------------ 1 file changed, 343 insertions(+), 324 deletions(-) diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 982f422134..deac450eea 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -88,336 +88,355 @@ export function useButtonContext() { return React.useContext(Context) } -export function Button({ - children, - variant, - color, - size, - shape = 'default', - label, - disabled = false, - style, - hoverStyle: hoverStyleProp, - ...rest -}: ButtonProps) { - const t = useTheme() - const [state, setState] = React.useState({ - pressed: false, - hovered: false, - focused: false, - }) - - const onPressIn = React.useCallback(() => { - setState(s => ({ - ...s, - pressed: true, - })) - }, [setState]) - const onPressOut = React.useCallback(() => { - setState(s => ({ - ...s, - pressed: false, - })) - }, [setState]) - const onHoverIn = React.useCallback(() => { - setState(s => ({ - ...s, - hovered: true, - })) - }, [setState]) - const onHoverOut = React.useCallback(() => { - setState(s => ({ - ...s, - hovered: false, - })) - }, [setState]) - const onFocus = React.useCallback(() => { - setState(s => ({ - ...s, - focused: true, - })) - }, [setState]) - const onBlur = React.useCallback(() => { - setState(s => ({ - ...s, - focused: false, - })) - }, [setState]) - - const {baseStyles, hoverStyles} = React.useMemo(() => { - const baseStyles: ViewStyle[] = [] - const hoverStyles: ViewStyle[] = [] - const light = t.name === 'light' - - if (color === 'primary') { - if (variant === 'solid') { - if (!disabled) { - baseStyles.push({ - backgroundColor: t.palette.primary_500, - }) - hoverStyles.push({ - backgroundColor: t.palette.primary_600, - }) - } else { - baseStyles.push({ - backgroundColor: t.palette.primary_700, - }) - } - } else if (variant === 'outline') { - baseStyles.push(a.border, t.atoms.bg, { - borderWidth: 1, - }) - - if (!disabled) { - baseStyles.push(a.border, { - borderColor: t.palette.primary_500, - }) - hoverStyles.push(a.border, { - backgroundColor: light - ? t.palette.primary_50 - : t.palette.primary_950, - }) - } else { - baseStyles.push(a.border, { - borderColor: light ? t.palette.primary_200 : t.palette.primary_900, - }) - } - } else if (variant === 'ghost') { - if (!disabled) { - baseStyles.push(t.atoms.bg) - hoverStyles.push({ - backgroundColor: light - ? t.palette.primary_100 - : t.palette.primary_900, - }) - } - } - } else if (color === 'secondary') { - if (variant === 'solid') { - if (!disabled) { - baseStyles.push({ - backgroundColor: t.palette.contrast_25, - }) - hoverStyles.push({ - backgroundColor: t.palette.contrast_50, - }) - } else { - baseStyles.push({ - backgroundColor: t.palette.contrast_100, - }) - } - } else if (variant === 'outline') { - baseStyles.push(a.border, t.atoms.bg, { - borderWidth: 1, - }) - - if (!disabled) { - baseStyles.push(a.border, { - borderColor: t.palette.contrast_300, - }) - hoverStyles.push(t.atoms.bg_contrast_50) - } else { - baseStyles.push(a.border, { - borderColor: t.palette.contrast_200, - }) - } - } else if (variant === 'ghost') { - if (!disabled) { - baseStyles.push(t.atoms.bg) - hoverStyles.push({ - backgroundColor: t.palette.contrast_100, - }) - } - } - } else if (color === 'negative') { - if (variant === 'solid') { - if (!disabled) { - baseStyles.push({ - backgroundColor: t.palette.negative_500, - }) - hoverStyles.push({ - backgroundColor: t.palette.negative_600, - }) - } else { - baseStyles.push({ - backgroundColor: t.palette.negative_700, - }) - } - } else if (variant === 'outline') { - baseStyles.push(a.border, t.atoms.bg, { - borderWidth: 1, - }) - - if (!disabled) { - baseStyles.push(a.border, { - borderColor: t.palette.negative_500, - }) - hoverStyles.push(a.border, { - backgroundColor: light - ? t.palette.negative_50 - : t.palette.negative_975, - }) - } else { - baseStyles.push(a.border, { - borderColor: light - ? t.palette.negative_200 - : t.palette.negative_900, - }) - } - } else if (variant === 'ghost') { - if (!disabled) { - baseStyles.push(t.atoms.bg) - hoverStyles.push({ - backgroundColor: light - ? t.palette.negative_100 - : t.palette.negative_975, - }) - } - } - } - - if (shape === 'default') { - if (size === 'large') { - baseStyles.push({paddingVertical: 15}, a.px_2xl, a.rounded_sm, a.gap_md) - } else if (size === 'medium') { - baseStyles.push({paddingVertical: 12}, a.px_2xl, a.rounded_sm, a.gap_md) - } else if (size === 'small') { - baseStyles.push({paddingVertical: 9}, a.px_lg, a.rounded_sm, a.gap_sm) - } else if (size === 'xsmall') { - baseStyles.push({paddingVertical: 6}, a.px_sm, a.rounded_sm, a.gap_sm) - } else if (size === 'tiny') { - baseStyles.push({paddingVertical: 4}, a.px_sm, a.rounded_xs, a.gap_xs) - } - } else if (shape === 'round' || shape === 'square') { - if (size === 'large') { - if (shape === 'round') { - baseStyles.push({height: 54, width: 54}) - } else { - baseStyles.push({height: 50, width: 50}) - } - } else if (size === 'small') { - baseStyles.push({height: 34, width: 34}) - } else if (size === 'xsmall') { - baseStyles.push({height: 28, width: 28}) - } else if (size === 'tiny') { - baseStyles.push({height: 20, width: 20}) - } - - if (shape === 'round') { - baseStyles.push(a.rounded_full) - } else if (shape === 'square') { - if (size === 'tiny') { - baseStyles.push(a.rounded_xs) - } else { - baseStyles.push(a.rounded_sm) - } - } - } - - return { - baseStyles, - hoverStyles, - } - }, [t, variant, color, size, shape, disabled]) - - const {gradientColors, gradientHoverColors, gradientLocations} = - React.useMemo(() => { - const colors: string[] = [] - const hoverColors: string[] = [] - const locations: number[] = [] - const gradient = { - primary: tokens.gradients.sky, - secondary: tokens.gradients.sky, - negative: tokens.gradients.sky, - gradient_sky: tokens.gradients.sky, - gradient_midnight: tokens.gradients.midnight, - gradient_sunrise: tokens.gradients.sunrise, - gradient_sunset: tokens.gradients.sunset, - gradient_nordic: tokens.gradients.nordic, - gradient_bonfire: tokens.gradients.bonfire, - }[color || 'primary'] - - if (variant === 'gradient') { - colors.push(...gradient.values.map(([_, color]) => color)) - hoverColors.push(...gradient.values.map(_ => gradient.hover_value)) - locations.push(...gradient.values.map(([location, _]) => location)) - } - - return { - gradientColors: colors, - gradientHoverColors: hoverColors, - gradientLocations: locations, - } - }, [variant, color]) - - const context = React.useMemo( - () => ({ - ...state, +export const Button = React.forwardRef( + ( + { + children, variant, color, size, - disabled: disabled || false, - }), - [state, variant, color, size, disabled], - ) + shape = 'default', + label, + disabled = false, + style, + hoverStyle: hoverStyleProp, + ...rest + }, + ref, + ) => { + const t = useTheme() + const [state, setState] = React.useState({ + pressed: false, + hovered: false, + focused: false, + }) - const flattenedBaseStyles = flatten(baseStyles) + const onPressIn = React.useCallback(() => { + setState(s => ({ + ...s, + pressed: true, + })) + }, [setState]) + const onPressOut = React.useCallback(() => { + setState(s => ({ + ...s, + pressed: false, + })) + }, [setState]) + const onHoverIn = React.useCallback(() => { + setState(s => ({ + ...s, + hovered: true, + })) + }, [setState]) + const onHoverOut = React.useCallback(() => { + setState(s => ({ + ...s, + hovered: false, + })) + }, [setState]) + const onFocus = React.useCallback(() => { + setState(s => ({ + ...s, + focused: true, + })) + }, [setState]) + const onBlur = React.useCallback(() => { + setState(s => ({ + ...s, + focused: false, + })) + }, [setState]) - return ( - { + const baseStyles: ViewStyle[] = [] + const hoverStyles: ViewStyle[] = [] + const light = t.name === 'light' + + if (color === 'primary') { + if (variant === 'solid') { + if (!disabled) { + baseStyles.push({ + backgroundColor: t.palette.primary_500, + }) + hoverStyles.push({ + backgroundColor: t.palette.primary_600, + }) + } else { + baseStyles.push({ + backgroundColor: t.palette.primary_700, + }) + } + } else if (variant === 'outline') { + baseStyles.push(a.border, t.atoms.bg, { + borderWidth: 1, + }) + + if (!disabled) { + baseStyles.push(a.border, { + borderColor: t.palette.primary_500, + }) + hoverStyles.push(a.border, { + backgroundColor: light + ? t.palette.primary_50 + : t.palette.primary_950, + }) + } else { + baseStyles.push(a.border, { + borderColor: light + ? t.palette.primary_200 + : t.palette.primary_900, + }) + } + } else if (variant === 'ghost') { + if (!disabled) { + baseStyles.push(t.atoms.bg) + hoverStyles.push({ + backgroundColor: light + ? t.palette.primary_100 + : t.palette.primary_900, + }) + } + } + } else if (color === 'secondary') { + if (variant === 'solid') { + if (!disabled) { + baseStyles.push({ + backgroundColor: t.palette.contrast_25, + }) + hoverStyles.push({ + backgroundColor: t.palette.contrast_50, + }) + } else { + baseStyles.push({ + backgroundColor: t.palette.contrast_100, + }) + } + } else if (variant === 'outline') { + baseStyles.push(a.border, t.atoms.bg, { + borderWidth: 1, + }) + + if (!disabled) { + baseStyles.push(a.border, { + borderColor: t.palette.contrast_300, + }) + hoverStyles.push(t.atoms.bg_contrast_50) + } else { + baseStyles.push(a.border, { + borderColor: t.palette.contrast_200, + }) + } + } else if (variant === 'ghost') { + if (!disabled) { + baseStyles.push(t.atoms.bg) + hoverStyles.push({ + backgroundColor: t.palette.contrast_100, + }) + } + } + } else if (color === 'negative') { + if (variant === 'solid') { + if (!disabled) { + baseStyles.push({ + backgroundColor: t.palette.negative_500, + }) + hoverStyles.push({ + backgroundColor: t.palette.negative_600, + }) + } else { + baseStyles.push({ + backgroundColor: t.palette.negative_700, + }) + } + } else if (variant === 'outline') { + baseStyles.push(a.border, t.atoms.bg, { + borderWidth: 1, + }) + + if (!disabled) { + baseStyles.push(a.border, { + borderColor: t.palette.negative_500, + }) + hoverStyles.push(a.border, { + backgroundColor: light + ? t.palette.negative_50 + : t.palette.negative_975, + }) + } else { + baseStyles.push(a.border, { + borderColor: light + ? t.palette.negative_200 + : t.palette.negative_900, + }) + } + } else if (variant === 'ghost') { + if (!disabled) { + baseStyles.push(t.atoms.bg) + hoverStyles.push({ + backgroundColor: light + ? t.palette.negative_100 + : t.palette.negative_975, + }) + } + } + } + + if (shape === 'default') { + if (size === 'large') { + baseStyles.push( + {paddingVertical: 15}, + a.px_2xl, + a.rounded_sm, + a.gap_md, + ) + } else if (size === 'medium') { + baseStyles.push( + {paddingVertical: 12}, + a.px_2xl, + a.rounded_sm, + a.gap_md, + ) + } else if (size === 'small') { + baseStyles.push({paddingVertical: 9}, a.px_lg, a.rounded_sm, a.gap_sm) + } else if (size === 'xsmall') { + baseStyles.push({paddingVertical: 6}, a.px_sm, a.rounded_sm, a.gap_sm) + } else if (size === 'tiny') { + baseStyles.push({paddingVertical: 4}, a.px_sm, a.rounded_xs, a.gap_xs) + } + } else if (shape === 'round' || shape === 'square') { + if (size === 'large') { + if (shape === 'round') { + baseStyles.push({height: 54, width: 54}) + } else { + baseStyles.push({height: 50, width: 50}) + } + } else if (size === 'small') { + baseStyles.push({height: 34, width: 34}) + } else if (size === 'xsmall') { + baseStyles.push({height: 28, width: 28}) + } else if (size === 'tiny') { + baseStyles.push({height: 20, width: 20}) + } + + if (shape === 'round') { + baseStyles.push(a.rounded_full) + } else if (shape === 'square') { + if (size === 'tiny') { + baseStyles.push(a.rounded_xs) + } else { + baseStyles.push(a.rounded_sm) + } + } + } + + return { + baseStyles, + hoverStyles, + } + }, [t, variant, color, size, shape, disabled]) + + const {gradientColors, gradientHoverColors, gradientLocations} = + React.useMemo(() => { + const colors: string[] = [] + const hoverColors: string[] = [] + const locations: number[] = [] + const gradient = { + primary: tokens.gradients.sky, + secondary: tokens.gradients.sky, + negative: tokens.gradients.sky, + gradient_sky: tokens.gradients.sky, + gradient_midnight: tokens.gradients.midnight, + gradient_sunrise: tokens.gradients.sunrise, + gradient_sunset: tokens.gradients.sunset, + gradient_nordic: tokens.gradients.nordic, + gradient_bonfire: tokens.gradients.bonfire, + }[color || 'primary'] + + if (variant === 'gradient') { + colors.push(...gradient.values.map(([_, color]) => color)) + hoverColors.push(...gradient.values.map(_ => gradient.hover_value)) + locations.push(...gradient.values.map(([location, _]) => location)) + } + + return { + gradientColors: colors, + gradientHoverColors: hoverColors, + gradientLocations: locations, + } + }, [variant, color]) + + const context = React.useMemo( + () => ({ + ...state, + variant, + color, + size, disabled: disabled || false, - }} - style={[ - a.flex_row, - a.align_center, - a.justify_center, - flattenedBaseStyles, - flatten(style), - ...(state.hovered || state.pressed - ? [hoverStyles, flatten(hoverStyleProp)] - : []), - ]} - onPressIn={onPressIn} - onPressOut={onPressOut} - onHoverIn={onHoverIn} - onHoverOut={onHoverOut} - onFocus={onFocus} - onBlur={onBlur}> - {variant === 'gradient' && ( - - - - )} - - {typeof children === 'function' ? children(context) : children} - - - ) -} + }), + [state, variant, color, size, disabled], + ) + + const flattenedBaseStyles = flatten(baseStyles) + + return ( + + {variant === 'gradient' && ( + + + + )} + + {typeof children === 'function' ? children(context) : children} + + + ) + }, +) +Button.displayName = 'Button' export function useSharedButtonTextStyles() { const t = useTheme() From bfcf8729213bbdad001765fffe55d4af9b5e03d5 Mon Sep 17 00:00:00 2001 From: Frudrax Cheng Date: Thu, 20 Jun 2024 09:13:20 +0800 Subject: [PATCH 067/127] CN: translated 'ALT' --- src/locale/locales/zh-CN/messages.po | 106 ++++++++++++++------------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/src/locale/locales/zh-CN/messages.po b/src/locale/locales/zh-CN/messages.po index 726e7bdd21..b33a89d7f9 100644 --- a/src/locale/locales/zh-CN/messages.po +++ b/src/locale/locales/zh-CN/messages.po @@ -33,7 +33,7 @@ msgstr "{0, plural, one {# 个标签已标记到这个账户} other {# 个标签 msgid "{0, plural, one {# label has been placed on this content} other {# labels have been placed on this content}}" msgstr "{0, plural, one {# 个标签已标记到此内容} other {# 个标签已标记到此内容}}" -#: src/view/com/util/post-ctrls/RepostButton.tsx:65 +#: src/view/com/util/post-ctrls/RepostButton.tsx:66 msgid "{0, plural, one {# repost} other {# reposts}}" msgstr "{0, plural, one {# 条转发} other {# 条转发}}" @@ -47,7 +47,7 @@ msgstr "{0, plural, one {关注者} other {关注者}}" msgid "{0, plural, one {following} other {following}}" msgstr "{0, plural, one {正在关注} other {正在关注}}" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:254 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:255 msgid "{0, plural, one {Like (# like)} other {Like (# likes)}}" msgstr "{0, plural, one {喜欢 (# 个喜欢)} other {喜欢 (# 个喜欢)}}" @@ -64,7 +64,7 @@ msgstr "{0, plural, one {# 位用户喜欢} other {# 位用户喜欢}}" msgid "{0, plural, one {post} other {posts}}" msgstr "{0, plural, one {帖文} other {帖文}}" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:212 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:213 msgid "{0, plural, one {Reply (# reply)} other {Reply (# replies)}}" msgstr "{0, plural, one {回复 (# 个回复)} other {回复 (# 个回复)}}" @@ -72,7 +72,7 @@ msgstr "{0, plural, one {回复 (# 个回复)} other {回复 (# 个回复)}}" msgid "{0, plural, one {repost} other {reposts}}" msgstr "{0, plural, one {转发} other {转发}}" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:250 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:251 msgid "{0, plural, one {Unlike (# like)} other {Unlike (# likes)}}" msgstr "{0, plural, one {取消喜欢 (# 个喜欢)} other {取消喜欢 (# 个喜欢)}}" @@ -182,7 +182,7 @@ msgid "Accessibility settings" msgstr "无障碍设置" #: src/Navigation.tsx:296 -#: src/view/screens/AccessibilitySettings.tsx:63 +#: src/view/screens/AccessibilitySettings.tsx:69 msgid "Accessibility Settings" msgstr "无障碍设置" @@ -263,7 +263,7 @@ msgstr "添加账户" #: src/view/com/composer/photos/Gallery.tsx:187 #: src/view/com/modals/AltImage.tsx:118 msgid "Add alt text" -msgstr "新增替代文字" +msgstr "新增替代文本" #: src/view/screens/AppPasswords.tsx:106 #: src/view/screens/AppPasswords.tsx:148 @@ -356,23 +356,23 @@ msgstr "已以@{0}身份登录" #: src/view/com/composer/GifAltText.tsx:93 #: src/view/com/composer/photos/Gallery.tsx:144 -#: src/view/com/util/post-embeds/GifEmbed.tsx:173 +#: src/view/com/util/post-embeds/GifEmbed.tsx:177 msgid "ALT" msgstr "ALT" #: src/view/com/composer/GifAltText.tsx:144 #: src/view/com/modals/EditImage.tsx:316 -#: src/view/screens/AccessibilitySettings.tsx:77 +#: src/view/screens/AccessibilitySettings.tsx:83 msgid "Alt text" -msgstr "替代文字" +msgstr "替代文本" -#: src/view/com/util/post-embeds/GifEmbed.tsx:179 +#: src/view/com/util/post-embeds/GifEmbed.tsx:183 msgid "Alt Text" -msgstr "替代文字" +msgstr "替代文本" #: src/view/com/composer/photos/Gallery.tsx:224 msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone." -msgstr "为图片新增替代文字,以帮助盲人及视障群体了解图片内容。" +msgstr "为图片新增替代文本,以帮助盲人及视障群体了解图片内容。" #: src/view/com/modals/VerifyEmail.tsx:132 #: src/view/screens/Settings/DisableEmail2FADialog.tsx:96 @@ -413,7 +413,7 @@ msgstr "和" msgid "Animals" msgstr "动物" -#: src/view/com/util/post-embeds/GifEmbed.tsx:148 +#: src/view/com/util/post-embeds/GifEmbed.tsx:149 msgid "Animated GIF" msgstr "GIF 动画" @@ -709,7 +709,7 @@ msgstr "只能包含字母、数字、空格、破折号及下划线。 长度 #: src/view/com/modals/LinkWarning.tsx:107 #: src/view/com/modals/VerifyEmail.tsx:255 #: src/view/com/modals/VerifyEmail.tsx:261 -#: src/view/com/util/post-ctrls/RepostButton.tsx:138 +#: src/view/com/util/post-ctrls/RepostButton.tsx:139 #: src/view/screens/Search/Search.tsx:704 #: src/view/shell/desktop/Search.tsx:218 msgid "Cancel" @@ -739,7 +739,7 @@ msgstr "取消裁剪图片" msgid "Cancel profile editing" msgstr "取消编辑个人资料" -#: src/view/com/util/post-ctrls/RepostButton.tsx:132 +#: src/view/com/util/post-ctrls/RepostButton.tsx:133 msgid "Cancel quote post" msgstr "取消引用帖文" @@ -917,7 +917,7 @@ msgstr "哒哒🐴哒哒🐴" #: src/components/dms/dialogs/SearchablePeopleList.tsx:261 #: src/view/com/modals/ChangePassword.tsx:268 #: src/view/com/modals/ChangePassword.tsx:271 -#: src/view/com/util/post-embeds/GifEmbed.tsx:185 +#: src/view/com/util/post-embeds/GifEmbed.tsx:189 msgid "Close" msgstr "关闭" @@ -1395,7 +1395,7 @@ msgstr "描述" #: src/view/com/composer/GifAltText.tsx:140 msgid "Descriptive alt text" -msgstr "描述替代文字" +msgstr "描述替代文本" #: src/view/com/composer/Composer.tsx:277 msgid "Did you want to say anything?" @@ -1409,7 +1409,7 @@ msgstr "暗淡" msgid "Direct messages are here!" msgstr "隆重介绍私信功能!" -#: src/view/screens/AccessibilitySettings.tsx:94 +#: src/view/screens/AccessibilitySettings.tsx:107 msgid "Disable autoplay for GIFs" msgstr "关闭 GIF 自动播放" @@ -1417,7 +1417,7 @@ msgstr "关闭 GIF 自动播放" msgid "Disable Email 2FA" msgstr "关闭电子邮件两步验证" -#: src/view/screens/AccessibilitySettings.tsx:108 +#: src/view/screens/AccessibilitySettings.tsx:121 msgid "Disable haptic feedback" msgstr "关闭触感反馈" @@ -1456,6 +1456,10 @@ msgstr "探索新的资讯源" msgid "Discover New Feeds" msgstr "探索新的资讯源" +#: src/view/screens/AccessibilitySettings.tsx:95 +msgid "Display larger alt text badges" +msgstr "显示更大的替代文本标签" + #: src/view/com/modals/EditProfile.tsx:193 msgid "Display name" msgstr "显示名称" @@ -2270,7 +2274,7 @@ msgstr "图形媒体" msgid "Handle" msgstr "用户识别符" -#: src/view/screens/AccessibilitySettings.tsx:103 +#: src/view/screens/AccessibilitySettings.tsx:116 msgid "Haptics" msgstr "触感" @@ -2439,7 +2443,7 @@ msgstr "如果你想更改你的用户识别符或电子邮件,请在停用之 msgid "Illegal and Urgent" msgstr "违法" -#: src/view/com/util/images/Gallery.tsx:39 +#: src/view/com/util/images/Gallery.tsx:42 msgid "Image" msgstr "图片" @@ -2824,7 +2828,7 @@ msgstr "管理你的隐藏词和标签" msgid "Mark as read" msgstr "标记为已读" -#: src/view/screens/AccessibilitySettings.tsx:89 +#: src/view/screens/AccessibilitySettings.tsx:102 #: src/view/screens/Profile.tsx:195 msgid "Media" msgstr "媒体" @@ -3305,7 +3309,7 @@ msgstr "暂时不需要" #: src/view/com/profile/ProfileMenu.tsx:373 #: src/view/com/util/forms/PostDropdownBtn.tsx:456 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:310 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:311 msgid "Note about sharing" msgstr "分享注意事项" @@ -3387,7 +3391,7 @@ msgstr "重新开始引导流程" #: src/view/com/composer/Composer.tsx:505 msgid "One or more images is missing alt text." -msgstr "至少有一张图片缺失了替代文字。" +msgstr "至少有一张图片缺失了替代文本。" #: src/screens/Onboarding/StepProfile/index.tsx:117 msgid "Only .jpg and .png files are supported" @@ -3655,7 +3659,7 @@ msgstr "密码已更新" msgid "Password updated!" msgstr "密码已更新!" -#: src/view/com/util/post-embeds/GifEmbed.tsx:36 +#: src/view/com/util/post-embeds/GifEmbed.tsx:37 msgid "Pause" msgstr "暂停" @@ -3704,7 +3708,7 @@ msgstr "固定资讯源列表" msgid "Pinned to your feeds" msgstr "固定到你的资讯源" -#: src/view/com/util/post-embeds/GifEmbed.tsx:36 +#: src/view/com/util/post-embeds/GifEmbed.tsx:37 msgid "Play" msgstr "播放" @@ -3712,7 +3716,7 @@ msgstr "播放" msgid "Play {0}" msgstr "播放 {0}" -#: src/view/com/util/post-embeds/GifEmbed.tsx:35 +#: src/view/com/util/post-embeds/GifEmbed.tsx:36 msgid "Play or pause the GIF" msgstr "播放或暂停 GIF" @@ -3955,10 +3959,10 @@ msgstr "发布帖文" msgid "Publish reply" msgstr "发布回复" -#: src/view/com/util/post-ctrls/RepostButton.tsx:115 -#: src/view/com/util/post-ctrls/RepostButton.tsx:127 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:78 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:81 +#: src/view/com/util/post-ctrls/RepostButton.tsx:116 +#: src/view/com/util/post-ctrls/RepostButton.tsx:128 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:79 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:82 msgid "Quote post" msgstr "引用帖文" @@ -4063,8 +4067,8 @@ msgstr "从搜索历史中删除个人资料" msgid "Remove quote" msgstr "删除引用" -#: src/view/com/util/post-ctrls/RepostButton.tsx:92 -#: src/view/com/util/post-ctrls/RepostButton.tsx:108 +#: src/view/com/util/post-ctrls/RepostButton.tsx:93 +#: src/view/com/util/post-ctrls/RepostButton.tsx:109 msgid "Remove repost" msgstr "删除转发" @@ -4193,21 +4197,21 @@ msgstr "举报这条帖文" msgid "Report this user" msgstr "举报这个用户" -#: src/view/com/util/post-ctrls/RepostButton.tsx:64 -#: src/view/com/util/post-ctrls/RepostButton.tsx:93 -#: src/view/com/util/post-ctrls/RepostButton.tsx:109 +#: src/view/com/util/post-ctrls/RepostButton.tsx:65 +#: src/view/com/util/post-ctrls/RepostButton.tsx:94 +#: src/view/com/util/post-ctrls/RepostButton.tsx:110 msgctxt "action" msgid "Repost" msgstr "转发" -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:69 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:73 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:70 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:74 msgid "Repost" msgstr "转发" -#: src/view/com/util/post-ctrls/RepostButton.tsx:85 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:46 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:92 +#: src/view/com/util/post-ctrls/RepostButton.tsx:86 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:47 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:93 msgid "Repost or quote post" msgstr "转发或引用帖文" @@ -4241,7 +4245,7 @@ msgstr "请求变更" msgid "Request Code" msgstr "确认码" -#: src/view/screens/AccessibilitySettings.tsx:82 +#: src/view/screens/AccessibilitySettings.tsx:88 msgid "Require alt text before posting" msgstr "发布时检查媒体是否存在替代文本" @@ -4342,7 +4346,7 @@ msgstr "保存" #: src/view/com/modals/AltImage.tsx:132 msgid "Save alt text" -msgstr "保存替代文字" +msgstr "保存替代文本" #: src/components/dialogs/BirthDateSettings.tsx:119 msgid "Save birthday" @@ -4716,7 +4720,7 @@ msgstr "分享" #: src/view/com/profile/ProfileMenu.tsx:229 #: src/view/com/util/forms/PostDropdownBtn.tsx:307 #: src/view/com/util/forms/PostDropdownBtn.tsx:316 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:299 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:300 #: src/view/screens/ProfileList.tsx:428 msgid "Share" msgstr "分享" @@ -4731,7 +4735,7 @@ msgstr "分享一个有趣的事实!" #: src/view/com/profile/ProfileMenu.tsx:378 #: src/view/com/util/forms/PostDropdownBtn.tsx:461 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:315 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:316 msgid "Share anyway" msgstr "仍然分享" @@ -4760,9 +4764,9 @@ msgstr "分享链接的网站" msgid "Show" msgstr "显示" -#: src/view/com/util/post-embeds/GifEmbed.tsx:167 +#: src/view/com/util/post-embeds/GifEmbed.tsx:169 msgid "Show alt text" -msgstr "显示替代文字" +msgstr "显示替代文本" #: src/components/moderation/ScreenHider.tsx:169 #: src/components/moderation/ScreenHider.tsx:172 @@ -5395,7 +5399,7 @@ msgid "This post has been deleted." msgstr "这条帖文已被删除。" #: src/view/com/util/forms/PostDropdownBtn.tsx:458 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:312 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:313 msgid "This post is only visible to logged-in users. It won't be visible to people who aren't logged in." msgstr "这条帖文只对已登录用户可见,未登录的用户将无法看到。" @@ -5576,9 +5580,9 @@ msgstr "取消屏蔽账户" msgid "Unblock Account?" msgstr "取消屏蔽账户?" -#: src/view/com/util/post-ctrls/RepostButton.tsx:63 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:69 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:73 +#: src/view/com/util/post-ctrls/RepostButton.tsx:64 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:70 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:74 msgid "Undo repost" msgstr "取消转发" From 7deea7ddd450071f702f3aaf52a7763012e4e370 Mon Sep 17 00:00:00 2001 From: Frudrax Cheng Date: Thu, 20 Jun 2024 09:15:40 +0800 Subject: [PATCH 068/127] CN: Update Revision-Date --- src/locale/locales/zh-CN/messages.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locale/locales/zh-CN/messages.po b/src/locale/locales/zh-CN/messages.po index b33a89d7f9..5d138524fc 100644 --- a/src/locale/locales/zh-CN/messages.po +++ b/src/locale/locales/zh-CN/messages.po @@ -8,7 +8,7 @@ msgstr "" "Language: zh_CN\n" "Project-Id-Version: zh-CN for bluesky-social-app\n" "Report-Msgid-Bugs-To: Frudrax Cheng \n" -"PO-Revision-Date: 2024-06-19 14:55+0800\n" +"PO-Revision-Date: 2024-06-20 09:15+0800\n" "Last-Translator: Frudrax Cheng \n" "Language-Team: Frudrax Cheng (auroursa), Simon Chan (RitsukiP), U2FsdGVkX1, Mikan Harada (mitian233), IceCodeNew\n" "Plural-Forms: \n" From 75aec19230609f8ae689cd1ec0b2697c5233bdeb Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 20 Jun 2024 04:19:37 +0300 Subject: [PATCH 069/127] Fix threadgate read after write (#4577) * Fix threadgate read-after-write problem * Fix React key (drive-by) --- src/view/com/threadgate/WhoCanReply.tsx | 43 ++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/view/com/threadgate/WhoCanReply.tsx b/src/view/com/threadgate/WhoCanReply.tsx index 3ffbaa7ae9..7e3528d928 100644 --- a/src/view/com/threadgate/WhoCanReply.tsx +++ b/src/view/com/threadgate/WhoCanReply.tsx @@ -1,12 +1,19 @@ import React from 'react' import {Keyboard, StyleProp, View, ViewStyle} from 'react-native' -import {AppBskyFeedDefs, AppBskyGraphDefs, AtUri} from '@atproto/api' +import { + AppBskyFeedDefs, + AppBskyFeedGetPostThread, + AppBskyGraphDefs, + AtUri, + BskyAgent, +} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' import {useAnalytics} from '#/lib/analytics/analytics' import {createThreadgate} from '#/lib/api' +import {until} from '#/lib/async/until' import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' import {usePalette} from '#/lib/hooks/usePalette' import {makeListLink, makeProfileLink} from '#/lib/routes/links' @@ -85,6 +92,18 @@ export function WhoCanReply({ rkey: new AtUri(post.uri).rkey, }) } + await whenAppViewReady(agent, post.uri, res => { + const thread = res.data.thread + if (AppBskyFeedDefs.isThreadViewPost(thread)) { + const fetchedSettings = threadgateViewToSettings( + thread.post.threadgate, + ) + return ( + JSON.stringify(fetchedSettings) === JSON.stringify(newSettings) + ) + } + return false + }) Toast.show('Thread settings updated') queryClient.invalidateQueries({ queryKey: [POST_THREAD_RQKEY_ROOT], @@ -133,15 +152,14 @@ export function WhoCanReply({ Only{' '} {settings.map((rule, i) => ( - <> + - + ))}{' '} can reply. @@ -227,3 +245,20 @@ function Separator({i, length}: {i: number; length: number}) { } return <>, } + +async function whenAppViewReady( + agent: BskyAgent, + uri: string, + fn: (res: AppBskyFeedGetPostThread.Response) => boolean, +) { + await until( + 5, // 5 tries + 1e3, // 1s delay between tries + fn, + () => + agent.app.bsky.feed.getPostThread({ + uri, + depth: 0, + }), + ) +} From 80197556f176723b619349dc060d1b7001472d47 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Wed, 19 Jun 2024 18:39:45 -0700 Subject: [PATCH 070/127] Rework "Who can reply" to blend more nicely into the UI (#4578) * Rework WhoCanReply controls in threads to blend more nicely * Fix layout * Fix post control hitslops * Move dialog content to separate component --------- Co-authored-by: Dan Abramov --- src/lib/constants.ts | 1 + src/view/com/post-thread/PostThreadItem.tsx | 38 +- src/view/com/threadgate/WhoCanReply.tsx | 447 +++++++++++------- src/view/com/util/post-ctrls/PostCtrls.tsx | 10 +- src/view/com/util/post-ctrls/RepostButton.tsx | 4 +- 5 files changed, 314 insertions(+), 186 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 05d1591f56..e0b8998007 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -84,6 +84,7 @@ export const createHitslop = (size: number): Insets => ({ export const HITSLOP_10 = createHitslop(10) export const HITSLOP_20 = createHitslop(20) export const HITSLOP_30 = createHitslop(30) +export const POST_CTRL_HITSLOP = {top: 5, bottom: 10, left: 10, right: 10} export const BACK_HITSLOP = HITSLOP_30 export const MAX_POST_LINES = 25 diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 6d03029d7f..92b529db78 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -25,7 +25,7 @@ import {sanitizeHandle} from 'lib/strings/handles' import {countLines} from 'lib/strings/helpers' import {niceDate} from 'lib/strings/time' import {s} from 'lib/styles' -import {isNative, isWeb} from 'platform/detection' +import {isWeb} from 'platform/detection' import {useSession} from 'state/session' import {PostThreadFollowBtn} from 'view/com/post-thread/PostThreadFollowBtn' import {atoms as a} from '#/alf' @@ -35,7 +35,7 @@ import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' import {PostAlerts} from '../../../components/moderation/PostAlerts' import {PostHider} from '../../../components/moderation/PostHider' import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers' -import {WhoCanReply} from '../threadgate/WhoCanReply' +import {WhoCanReplyBlock, WhoCanReplyInline} from '../threadgate/WhoCanReply' import {ErrorMessage} from '../util/error/ErrorMessage' import {Link, TextLink} from '../util/Link' import {formatCount} from '../util/numeric/format' @@ -340,6 +340,7 @@ let PostThreadItemLoaded = ({ @@ -396,11 +397,6 @@ let PostThreadItemLoaded = ({
- ) } else { @@ -579,14 +575,7 @@ let PostThreadItemLoaded = ({ ) : undefined} - + ) } @@ -654,10 +643,12 @@ function PostOuterWrapper({ function ExpandedPostDetails({ post, + isThreadAuthor, needsTranslation, translatorUrl, }: { post: AppBskyFeedDefs.PostView + isThreadAuthor: boolean needsTranslation: boolean translatorUrl: string }) { @@ -670,14 +661,23 @@ function ExpandedPostDetails({ }, [openLink, translatorUrl]) return ( - - {niceDate(post.indexedAt)} + + {niceDate(post.indexedAt)} + {needsTranslation && ( <> - · + · Translate diff --git a/src/view/com/threadgate/WhoCanReply.tsx b/src/view/com/threadgate/WhoCanReply.tsx index 7e3528d928..3f9970f5fb 100644 --- a/src/view/com/threadgate/WhoCanReply.tsx +++ b/src/view/com/threadgate/WhoCanReply.tsx @@ -11,13 +11,10 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' -import {useAnalytics} from '#/lib/analytics/analytics' import {createThreadgate} from '#/lib/api' import {until} from '#/lib/async/until' -import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' -import {usePalette} from '#/lib/hooks/usePalette' +import {HITSLOP_10} from '#/lib/constants' import {makeListLink, makeProfileLink} from '#/lib/routes/links' -import {colors} from '#/lib/styles' import {logger} from '#/logger' import {isNative} from '#/platform/detection' import {useModalControls} from '#/state/modals' @@ -28,45 +25,301 @@ import { } from '#/state/queries/threadgate' import {useAgent} from '#/state/session' import * as Toast from 'view/com/util/Toast' +import {atoms as a, useTheme} from '#/alf' import {Button} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {useDialogControl} from '#/components/Dialog' +import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign' +import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe' +import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group' +import {Text} from '#/components/Typography' import {TextLink} from '../util/Link' -import {Text} from '../util/text/Text' -export function WhoCanReply({ - post, - isThreadAuthor, - style, -}: { +interface WhoCanReplyProps { post: AppBskyFeedDefs.PostView isThreadAuthor: boolean style?: StyleProp -}) { - const {track} = useAnalytics() +} + +export function WhoCanReplyInline({ + post, + isThreadAuthor, + style, +}: WhoCanReplyProps) { const {_} = useLingui() - const pal = usePalette('default') + const t = useTheme() + const infoDialogControl = useDialogControl() + const {settings, isRootPost, onPressEdit} = useWhoCanReply(post) + + if (!isRootPost) { + return null + } + if (!settings.length && !isThreadAuthor) { + return null + } + + const isEverybody = settings.length === 0 + const isNobody = !!settings.find(gate => gate.type === 'nobody') + const description = isEverybody + ? _(msg`Everybody can reply`) + : isNobody + ? _(msg`Replies disabled`) + : _(msg`Some people can reply`) + + return ( + <> + + + + ) +} + +export function WhoCanReplyBlock({ + post, + isThreadAuthor, + style, +}: WhoCanReplyProps) { + const {_} = useLingui() + const t = useTheme() + const infoDialogControl = useDialogControl() + const {settings, isRootPost, onPressEdit} = useWhoCanReply(post) + + if (!isRootPost) { + return null + } + if (!settings.length && !isThreadAuthor) { + return null + } + + const isEverybody = settings.length === 0 + const isNobody = !!settings.find(gate => gate.type === 'nobody') + const description = isEverybody + ? _(msg`Everybody can reply`) + : isNobody + ? _(msg`Replies on this thread are disabled`) + : _(msg`Some people can reply`) + + return ( + <> + + + + ) +} + +function Icon({ + color, + width, + settings, +}: { + color: string + width?: number + settings: ThreadgateSetting[] +}) { + const isEverybody = settings.length === 0 + const isNobody = !!settings.find(gate => gate.type === 'nobody') + const IconComponent = isEverybody ? Earth : isNobody ? CircleBanSign : Group + return +} + +function InfoDialog({ + control, + post, + settings, +}: { + control: Dialog.DialogControlProps + post: AppBskyFeedDefs.PostView + settings: ThreadgateSetting[] +}) { + return ( + + + + + ) +} + +function InfoDialogInner({ + post, + settings, +}: { + post: AppBskyFeedDefs.PostView + settings: ThreadgateSetting[] +}) { + const {_} = useLingui() + return ( + + + + Who can reply? + + + + + ) +} + +function Rules({ + post, + settings, +}: { + post: AppBskyFeedDefs.PostView + settings: ThreadgateSetting[] +}) { + const t = useTheme() + return ( + + {!settings.length ? ( + Everybody can reply + ) : settings[0].type === 'nobody' ? ( + Replies to this thread are disabled + ) : ( + + Only{' '} + {settings.map((rule, i) => ( + <> + + + + ))}{' '} + can reply + + )} + + ) +} + +function Rule({ + rule, + post, + lists, +}: { + rule: ThreadgateSetting + post: AppBskyFeedDefs.PostView + lists: AppBskyGraphDefs.ListViewBasic[] | undefined +}) { + const t = useTheme() + if (rule.type === 'mention') { + return mentioned users + } + if (rule.type === 'following') { + return ( + + users followed by{' '} + + + ) + } + if (rule.type === 'list') { + const list = lists?.find(l => l.uri === rule.list) + if (list) { + const listUrip = new AtUri(list.uri) + return ( + + {' '} + members + + ) + } + } +} + +function Separator({i, length}: {i: number; length: number}) { + if (length < 2 || i === length - 1) { + return null + } + if (i === length - 2) { + return ( + <> + {length > 2 ? ',' : ''} and{' '} + + ) + } + return <>, +} + +function useWhoCanReply(post: AppBskyFeedDefs.PostView) { const agent = useAgent() const queryClient = useQueryClient() const {openModal} = useModalControls() - const containerStyles = useColorSchemeStyle( - { - backgroundColor: pal.colors.unreadNotifBg, - }, - { - backgroundColor: pal.colors.unreadNotifBg, - }, - ) - const textStyles = useColorSchemeStyle( - {color: colors.blue5}, - {color: colors.blue1}, - ) - const hoverStyles = useColorSchemeStyle( - { - backgroundColor: colors.white, - }, - { - backgroundColor: pal.colors.background, - }, - ) + const settings = React.useMemo( () => threadgateViewToSettings(post.threadgate), [post], @@ -74,7 +327,6 @@ export function WhoCanReply({ const isRootPost = !('reply' in post.record) const onPressEdit = () => { - track('Post:EditThreadgateOpened') if (isNative && Keyboard.isVisible()) { Keyboard.dismiss() } @@ -108,7 +360,6 @@ export function WhoCanReply({ queryClient.invalidateQueries({ queryKey: [POST_THREAD_RQKEY_ROOT], }) - track('Post:ThreadgateEdited') } catch (err) { Toast.show( 'There was an issue. Please check your internet connection and try again.', @@ -119,131 +370,7 @@ export function WhoCanReply({ }) } - if (!isRootPost) { - return null - } - if (!settings.length && !isThreadAuthor) { - return null - } - - return ( - - - - {!settings.length ? ( - Everybody can reply. - ) : settings[0].type === 'nobody' ? ( - Replies to this thread are disabled. - ) : ( - - Only{' '} - {settings.map((rule, i) => ( - - - - - ))}{' '} - can reply. - - )} - - - {isThreadAuthor && ( - - - - )} - - ) -} - -function Rule({ - rule, - post, - lists, -}: { - rule: ThreadgateSetting - post: AppBskyFeedDefs.PostView - lists: AppBskyGraphDefs.ListViewBasic[] | undefined -}) { - const pal = usePalette('default') - if (rule.type === 'mention') { - return mentioned users - } - if (rule.type === 'following') { - return ( - - users followed by{' '} - - - ) - } - if (rule.type === 'list') { - const list = lists?.find(l => l.uri === rule.list) - if (list) { - const listUrip = new AtUri(list.uri) - return ( - - {' '} - members - - ) - } - } -} - -function Separator({i, length}: {i: number; length: number}) { - if (length < 2 || i === length - 1) { - return null - } - if (i === length - 2) { - return ( - <> - {length > 2 ? ',' : ''} and{' '} - - ) - } - return <>, + return {settings, isRootPost, onPressEdit} } async function whenAppViewReady( diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 55fb4a334b..472ce4043a 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -15,7 +15,7 @@ import { import {msg, plural} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {HITSLOP_10, HITSLOP_20} from '#/lib/constants' +import {POST_CTRL_HITSLOP} from '#/lib/constants' import {useHaptics} from '#/lib/haptics' import {makeProfileLink} from '#/lib/routes/links' import {shareUrl} from '#/lib/sharing' @@ -215,7 +215,7 @@ let PostCtrls = ({ other: 'Reply (# replies)', })} accessibilityHint="" - hitSlop={big ? HITSLOP_20 : HITSLOP_10}> + hitSlop={POST_CTRL_HITSLOP}> + hitSlop={POST_CTRL_HITSLOP}> {post.viewer?.like ? ( ) : ( @@ -299,7 +299,7 @@ let PostCtrls = ({ }} accessibilityLabel={_(msg`Share`)} accessibilityHint="" - hitSlop={big ? HITSLOP_20 : HITSLOP_10}> + hitSlop={POST_CTRL_HITSLOP}> diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx index 10bc369b8f..d49cda442c 100644 --- a/src/view/com/util/post-ctrls/RepostButton.tsx +++ b/src/view/com/util/post-ctrls/RepostButton.tsx @@ -3,7 +3,7 @@ import {View} from 'react-native' import {msg, plural} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {HITSLOP_10, HITSLOP_20} from '#/lib/constants' +import {POST_CTRL_HITSLOP} from '#/lib/constants' import {useHaptics} from '#/lib/haptics' import {useRequireAuth} from '#/state/session' import {atoms as a, useTheme} from '#/alf' @@ -67,7 +67,7 @@ let RepostButton = ({ shape="round" variant="ghost" color="secondary" - hitSlop={big ? HITSLOP_20 : HITSLOP_10}> + hitSlop={POST_CTRL_HITSLOP}> {typeof repostCount !== 'undefined' && repostCount > 0 ? ( Date: Thu, 20 Jun 2024 16:32:52 +0800 Subject: [PATCH 071/127] CN: Update translates --- src/locale/locales/zh-CN/messages.po | 68 +++++++++++++++++++++------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/src/locale/locales/zh-CN/messages.po b/src/locale/locales/zh-CN/messages.po index 5d138524fc..348ec6763b 100644 --- a/src/locale/locales/zh-CN/messages.po +++ b/src/locale/locales/zh-CN/messages.po @@ -8,7 +8,7 @@ msgstr "" "Language: zh_CN\n" "Project-Id-Version: zh-CN for bluesky-social-app\n" "Report-Msgid-Bugs-To: Frudrax Cheng \n" -"PO-Revision-Date: 2024-06-20 09:15+0800\n" +"PO-Revision-Date: 2024-06-20 16:32+0800\n" "Last-Translator: Frudrax Cheng \n" "Language-Team: Frudrax Cheng (auroursa), Simon Chan (RitsukiP), U2FsdGVkX1, Mikan Harada (mitian233), IceCodeNew\n" "Plural-Forms: \n" @@ -51,7 +51,7 @@ msgstr "{0, plural, one {正在关注} other {正在关注}}" msgid "{0, plural, one {Like (# like)} other {Like (# likes)}}" msgstr "{0, plural, one {喜欢 (# 个喜欢)} other {喜欢 (# 个喜欢)}}" -#: src/view/com/post-thread/PostThreadItem.tsx:381 +#: src/view/com/post-thread/PostThreadItem.tsx:382 msgid "{0, plural, one {like} other {likes}}" msgstr "{0, plural, one {喜欢} other {喜欢}}" @@ -68,7 +68,7 @@ msgstr "{0, plural, one {帖文} other {帖文}}" msgid "{0, plural, one {Reply (# reply)} other {Reply (# replies)}}" msgstr "{0, plural, one {回复 (# 个回复)} other {回复 (# 个回复)}}" -#: src/view/com/post-thread/PostThreadItem.tsx:361 +#: src/view/com/post-thread/PostThreadItem.tsx:362 msgid "{0, plural, one {repost} other {reposts}}" msgstr "{0, plural, one {转发} other {转发}}" @@ -139,7 +139,7 @@ msgstr "{profileName} 在 {0} 前加入了 Bluesky" msgid "{value, plural, =0 {Show all replies} one {Show replies with at least # like} other {Show replies with at least # likes}}" msgstr "{value, plural, =0 {显示所有回复} one {显示至少含有 # 个喜欢数的回复} other {显示至少含有 # 个喜欢数的回复}}" -#: src/view/com/threadgate/WhoCanReply.tsx:203 +#: src/view/com/threadgate/WhoCanReply.tsx:290 msgid "<0/> members" msgstr "<0/> 个成员" @@ -405,7 +405,7 @@ msgid "an unknown error occurred" msgstr "出现未知错误" #: src/view/com/notifications/FeedItem.tsx:260 -#: src/view/com/threadgate/WhoCanReply.tsx:224 +#: src/view/com/threadgate/WhoCanReply.tsx:311 msgid "and" msgstr "和" @@ -1576,8 +1576,6 @@ msgctxt "action" msgid "Edit" msgstr "编辑" -#: src/view/com/threadgate/WhoCanReply.tsx:153 -#: src/view/com/threadgate/WhoCanReply.tsx:161 #: src/view/screens/Feeds.tsx:370 #: src/view/screens/Feeds.tsx:441 msgid "Edit" @@ -1626,6 +1624,11 @@ msgstr "编辑个人资料" msgid "Edit User List" msgstr "编辑用户列表" +#: src/view/com/threadgate/WhoCanReply.tsx:73 +#: src/view/com/threadgate/WhoCanReply.tsx:130 +msgid "Edit who can reply" +msgstr "编辑谁可以回复" + #: src/view/com/modals/EditProfile.tsx:194 msgid "Edit your display name" msgstr "编辑你的显示名称" @@ -1785,12 +1788,15 @@ msgid "Everybody" msgstr "所有人" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:46 +#: src/view/com/threadgate/WhoCanReply.tsx:64 +#: src/view/com/threadgate/WhoCanReply.tsx:121 +#: src/view/com/threadgate/WhoCanReply.tsx:235 msgid "Everybody can reply" msgstr "所有人都可以回复" #: src/view/com/threadgate/WhoCanReply.tsx:129 -msgid "Everybody can reply." -msgstr "所有人都可以回复" +#~ msgid "Everybody can reply." +#~ msgstr "所有人都可以回复" #: src/components/dms/MessagesNUX.tsx:131 #: src/components/dms/MessagesNUX.tsx:134 @@ -2833,7 +2839,7 @@ msgstr "标记为已读" msgid "Media" msgstr "媒体" -#: src/view/com/threadgate/WhoCanReply.tsx:183 +#: src/view/com/threadgate/WhoCanReply.tsx:270 msgid "mentioned users" msgstr "提到的用户" @@ -2942,7 +2948,7 @@ msgstr "内容审核工具" msgid "Moderator has chosen to set a general warning on the content." msgstr "由内容审核服务提供方对这段内容设置的一般警告。" -#: src/view/com/post-thread/PostThreadItem.tsx:571 +#: src/view/com/post-thread/PostThreadItem.tsx:567 msgid "More" msgstr "更多" @@ -3397,9 +3403,13 @@ msgstr "至少有一张图片缺失了替代文本。" msgid "Only .jpg and .png files are supported" msgstr "目前只支持上传 .jpg 或 .png 格式的图片文件" +#: src/view/com/threadgate/WhoCanReply.tsx:239 +msgid "Only {0} can reply" +msgstr "只有 {0} 可以回复" + #: src/view/com/threadgate/WhoCanReply.tsx:133 -msgid "Only {0} can reply." -msgstr "只有{0}可以回复。" +#~ msgid "Only {0} can reply." +#~ msgstr "只有{0}可以回复。" #: src/screens/Signup/StepHandle.tsx:98 msgid "Only contains letters, numbers, and hyphens" @@ -4108,9 +4118,21 @@ msgstr "替换为\"Discover\"" msgid "Replies" msgstr "回复" +#: src/view/com/threadgate/WhoCanReply.tsx:66 +msgid "Replies disabled" +msgstr "回复已被禁用" + +#: src/view/com/threadgate/WhoCanReply.tsx:123 +msgid "Replies on this thread are disabled" +msgstr "该讨论串的回复已被禁用" + +#: src/view/com/threadgate/WhoCanReply.tsx:237 +msgid "Replies to this thread are disabled" +msgstr "该讨论串的回复已被禁用" + #: src/view/com/threadgate/WhoCanReply.tsx:131 -msgid "Replies to this thread are disabled." -msgstr "该讨论串的回复已被禁用。" +#~ msgid "Replies to this thread are disabled." +#~ msgstr "该讨论串的回复已被禁用。" #: src/view/com/composer/Composer.tsx:477 msgctxt "action" @@ -4795,7 +4817,7 @@ msgstr "显示已隐藏的回复" msgid "Show less like this" msgstr "更少显示类似这样的" -#: src/view/com/post-thread/PostThreadItem.tsx:537 +#: src/view/com/post-thread/PostThreadItem.tsx:533 #: src/view/com/post/Post.tsx:227 #: src/view/com/posts/FeedItem.tsx:396 msgid "Show More" @@ -4932,6 +4954,8 @@ msgid "Software Dev" msgstr "程序开发" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 +#: src/view/com/threadgate/WhoCanReply.tsx:67 +#: src/view/com/threadgate/WhoCanReply.tsx:124 msgid "Some people can reply" msgstr "一些人可以回复" @@ -5806,7 +5830,7 @@ msgstr "用户名或电子邮箱" msgid "Users" msgstr "用户" -#: src/view/com/threadgate/WhoCanReply.tsx:187 +#: src/view/com/threadgate/WhoCanReply.tsx:274 msgid "users followed by <0/>" msgstr "关注 <0/> 的用户" @@ -6047,9 +6071,19 @@ msgid "Who can message you?" msgstr "谁可以给你发送私信?" #: src/view/com/modals/Threadgate.tsx:69 +#: src/view/com/threadgate/WhoCanReply.tsx:73 +#: src/view/com/threadgate/WhoCanReply.tsx:130 msgid "Who can reply" msgstr "谁可以回复" +#: src/view/com/threadgate/WhoCanReply.tsx:206 +msgid "Who can reply dialog" +msgstr "谁可以回复对话框" + +#: src/view/com/threadgate/WhoCanReply.tsx:210 +msgid "Who can reply?" +msgstr "谁可以回复?" + #: src/screens/Home/NoFeedsPinned.tsx:79 #: src/screens/Messages/List/index.tsx:185 msgid "Whoops!" From 43adc64e5f1b756628a2123ab3493d169c0506b0 Mon Sep 17 00:00:00 2001 From: Frudrax Cheng Date: Thu, 20 Jun 2024 16:34:26 +0800 Subject: [PATCH 072/127] CN: Remove superseded strings --- src/locale/locales/zh-CN/messages.po | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/locale/locales/zh-CN/messages.po b/src/locale/locales/zh-CN/messages.po index 348ec6763b..1b90e88aa3 100644 --- a/src/locale/locales/zh-CN/messages.po +++ b/src/locale/locales/zh-CN/messages.po @@ -8,7 +8,7 @@ msgstr "" "Language: zh_CN\n" "Project-Id-Version: zh-CN for bluesky-social-app\n" "Report-Msgid-Bugs-To: Frudrax Cheng \n" -"PO-Revision-Date: 2024-06-20 16:32+0800\n" +"PO-Revision-Date: 2024-06-20 16:34+0800\n" "Last-Translator: Frudrax Cheng \n" "Language-Team: Frudrax Cheng (auroursa), Simon Chan (RitsukiP), U2FsdGVkX1, Mikan Harada (mitian233), IceCodeNew\n" "Plural-Forms: \n" @@ -1794,10 +1794,6 @@ msgstr "所有人" msgid "Everybody can reply" msgstr "所有人都可以回复" -#: src/view/com/threadgate/WhoCanReply.tsx:129 -#~ msgid "Everybody can reply." -#~ msgstr "所有人都可以回复" - #: src/components/dms/MessagesNUX.tsx:131 #: src/components/dms/MessagesNUX.tsx:134 #: src/screens/Messages/Settings.tsx:75 @@ -3407,10 +3403,6 @@ msgstr "目前只支持上传 .jpg 或 .png 格式的图片文件" msgid "Only {0} can reply" msgstr "只有 {0} 可以回复" -#: src/view/com/threadgate/WhoCanReply.tsx:133 -#~ msgid "Only {0} can reply." -#~ msgstr "只有{0}可以回复。" - #: src/screens/Signup/StepHandle.tsx:98 msgid "Only contains letters, numbers, and hyphens" msgstr "仅限字母、数字和连字符" @@ -4130,10 +4122,6 @@ msgstr "该讨论串的回复已被禁用" msgid "Replies to this thread are disabled" msgstr "该讨论串的回复已被禁用" -#: src/view/com/threadgate/WhoCanReply.tsx:131 -#~ msgid "Replies to this thread are disabled." -#~ msgstr "该讨论串的回复已被禁用。" - #: src/view/com/composer/Composer.tsx:477 msgctxt "action" msgid "Reply" From 52b52617e14f35e05624f8417f0af50d0f36af07 Mon Sep 17 00:00:00 2001 From: Kuwa Lee Date: Thu, 20 Jun 2024 23:00:18 +0800 Subject: [PATCH 073/127] TW: Update and clean --- src/locale/locales/zh-TW/messages.po | 154 ++++++++++++++++----------- 1 file changed, 90 insertions(+), 64 deletions(-) diff --git a/src/locale/locales/zh-TW/messages.po b/src/locale/locales/zh-TW/messages.po index 49a020533f..37219cb613 100644 --- a/src/locale/locales/zh-TW/messages.po +++ b/src/locale/locales/zh-TW/messages.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: zh-TW for bluesky-social-app\n" "POT-Creation-Date: \n" "Report-Msgid-Bugs-To: Kuwa Lee , Frudrax Cheng \n" -"PO-Revision-Date: 2024-06-20 05:23+0800\n" +"PO-Revision-Date: 2024-06-20 23:00+0800\n" "Last-Translator: \n" "Language-Team: Frudrax Cheng , Kuwa Lee , noeFly, snowleo208, Kisaragi Hiu, Yi-Jyun Pan, toto6038, cirx1e\n" "Language: zh_TW\n" @@ -33,7 +33,7 @@ msgstr "{0, plural, one {該帳號有 # 個標記} other {該帳號有 # 個標 msgid "{0, plural, one {# label has been placed on this content} other {# labels have been placed on this content}}" msgstr "{0, plural, one {該內容有 # 個標記} other {該內容有 # 個標記}}" -#: src/view/com/util/post-ctrls/RepostButton.tsx:65 +#: src/view/com/util/post-ctrls/RepostButton.tsx:66 msgid "{0, plural, one {# repost} other {# reposts}}" msgstr "{0, plural, one {# 個轉貼} other {# 個轉貼}}" @@ -47,11 +47,11 @@ msgstr "{0, plural, one {個跟隨者} other {個跟隨者}}" msgid "{0, plural, one {following} other {following}}" msgstr "{0, plural, one {個跟隨中} other {個跟隨中}}" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:254 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:255 msgid "{0, plural, one {Like (# like)} other {Like (# likes)}}" msgstr "{0, plural, one {喜歡(# 個喜歡)} other {喜歡(# 個喜歡)}}" -#: src/view/com/post-thread/PostThreadItem.tsx:381 +#: src/view/com/post-thread/PostThreadItem.tsx:382 msgid "{0, plural, one {like} other {likes}}" msgstr "{0, plural, one {喜歡} other {喜歡}}" @@ -64,15 +64,15 @@ msgstr "{0, plural,one {# 個用戶已喜歡} other {# 個用戶已喜歡}}" msgid "{0, plural, one {post} other {posts}}" msgstr "{0, plural, one {則貼文} other {則貼文}}" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:212 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:213 msgid "{0, plural, one {Reply (# reply)} other {Reply (# replies)}}" msgstr "{0, plural, one {回覆(# 個回覆)} other {回覆(# 個回覆)}}" -#: src/view/com/post-thread/PostThreadItem.tsx:361 +#: src/view/com/post-thread/PostThreadItem.tsx:362 msgid "{0, plural, one {repost} other {reposts}}" msgstr "{0, plural, one {轉貼} other {轉貼}}" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:250 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:251 msgid "{0, plural, one {Unlike (# like)} other {Unlike (# likes)}}" msgstr "{0, plural, one {撤回喜歡(# 個喜歡)} other {撤回喜歡(# 個喜歡)}}" @@ -139,7 +139,7 @@ msgstr "{profileName} 在 {0} 前加入了 Bluesky" msgid "{value, plural, =0 {Show all replies} one {Show replies with at least # like} other {Show replies with at least # likes}}" msgstr "{value, plural, =0 {顯示所有回覆} one {顯示至少 # 個喜歡的回覆} other {顯示至少 # 個喜歡的回覆}}" -#: src/view/com/threadgate/WhoCanReply.tsx:203 +#: src/view/com/threadgate/WhoCanReply.tsx:290 msgid "<0/> members" msgstr "<0/> 個成員" @@ -182,7 +182,7 @@ msgid "Accessibility settings" msgstr "無障礙設定" #: src/Navigation.tsx:296 -#: src/view/screens/AccessibilitySettings.tsx:63 +#: src/view/screens/AccessibilitySettings.tsx:69 msgid "Accessibility Settings" msgstr "無障礙設定" @@ -356,17 +356,17 @@ msgstr "已以 @{0} 身份登入" #: src/view/com/composer/GifAltText.tsx:93 #: src/view/com/composer/photos/Gallery.tsx:144 -#: src/view/com/util/post-embeds/GifEmbed.tsx:173 +#: src/view/com/util/post-embeds/GifEmbed.tsx:177 msgid "ALT" msgstr "ALT" #: src/view/com/composer/GifAltText.tsx:144 #: src/view/com/modals/EditImage.tsx:316 -#: src/view/screens/AccessibilitySettings.tsx:77 +#: src/view/screens/AccessibilitySettings.tsx:83 msgid "Alt text" msgstr "替代文字" -#: src/view/com/util/post-embeds/GifEmbed.tsx:179 +#: src/view/com/util/post-embeds/GifEmbed.tsx:183 msgid "Alt Text" msgstr "替代文字" @@ -405,7 +405,7 @@ msgid "an unknown error occurred" msgstr "出現未知錯誤" #: src/view/com/notifications/FeedItem.tsx:260 -#: src/view/com/threadgate/WhoCanReply.tsx:224 +#: src/view/com/threadgate/WhoCanReply.tsx:311 msgid "and" msgstr "和" @@ -413,7 +413,7 @@ msgstr "和" msgid "Animals" msgstr "動物" -#: src/view/com/util/post-embeds/GifEmbed.tsx:148 +#: src/view/com/util/post-embeds/GifEmbed.tsx:149 msgid "Animated GIF" msgstr "GIF 動畫" @@ -709,7 +709,7 @@ msgstr "只能包含字母、數字、空格、破折號及底線。長度必須 #: src/view/com/modals/LinkWarning.tsx:107 #: src/view/com/modals/VerifyEmail.tsx:255 #: src/view/com/modals/VerifyEmail.tsx:261 -#: src/view/com/util/post-ctrls/RepostButton.tsx:138 +#: src/view/com/util/post-ctrls/RepostButton.tsx:139 #: src/view/screens/Search/Search.tsx:704 #: src/view/shell/desktop/Search.tsx:218 msgid "Cancel" @@ -739,7 +739,7 @@ msgstr "取消圖片裁剪" msgid "Cancel profile editing" msgstr "取消編輯個人檔案" -#: src/view/com/util/post-ctrls/RepostButton.tsx:132 +#: src/view/com/util/post-ctrls/RepostButton.tsx:133 msgid "Cancel quote post" msgstr "取消引用貼文" @@ -917,7 +917,7 @@ msgstr "達達的馬蹄🐴是美麗的錯誤🐴" #: src/components/dms/dialogs/SearchablePeopleList.tsx:261 #: src/view/com/modals/ChangePassword.tsx:268 #: src/view/com/modals/ChangePassword.tsx:271 -#: src/view/com/util/post-embeds/GifEmbed.tsx:185 +#: src/view/com/util/post-embeds/GifEmbed.tsx:189 msgid "Close" msgstr "關閉" @@ -1409,7 +1409,7 @@ msgstr "昏暗" msgid "Direct messages are here!" msgstr "私人訊息已推出!" -#: src/view/screens/AccessibilitySettings.tsx:94 +#: src/view/screens/AccessibilitySettings.tsx:107 msgid "Disable autoplay for GIFs" msgstr "關閉 GIF 自動播放" @@ -1417,7 +1417,7 @@ msgstr "關閉 GIF 自動播放" msgid "Disable Email 2FA" msgstr "關閉電子郵件雙重驗證" -#: src/view/screens/AccessibilitySettings.tsx:108 +#: src/view/screens/AccessibilitySettings.tsx:121 msgid "Disable haptic feedback" msgstr "關閉觸覺回饋" @@ -1456,6 +1456,10 @@ msgstr "探索新的動態源" msgid "Discover New Feeds" msgstr "探索新的動態源" +#: src/view/screens/AccessibilitySettings.tsx:95 +msgid "Display larger alt text badges" +msgstr "顯示更大的 alt 文本標識" + #: src/view/com/modals/EditProfile.tsx:193 msgid "Display name" msgstr "顯示名稱" @@ -1572,8 +1576,6 @@ msgctxt "action" msgid "Edit" msgstr "編輯" -#: src/view/com/threadgate/WhoCanReply.tsx:153 -#: src/view/com/threadgate/WhoCanReply.tsx:161 #: src/view/screens/Feeds.tsx:370 #: src/view/screens/Feeds.tsx:441 msgid "Edit" @@ -1622,6 +1624,11 @@ msgstr "編輯個人檔案" msgid "Edit User List" msgstr "編輯用戶列表" +#: src/view/com/threadgate/WhoCanReply.tsx:73 +#: src/view/com/threadgate/WhoCanReply.tsx:130 +msgid "Edit who can reply" +msgstr "編輯「誰可以回覆」" + #: src/view/com/modals/EditProfile.tsx:194 msgid "Edit your display name" msgstr "編輯您的顯示名稱" @@ -1781,13 +1788,12 @@ msgid "Everybody" msgstr "所有人" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:46 +#: src/view/com/threadgate/WhoCanReply.tsx:64 +#: src/view/com/threadgate/WhoCanReply.tsx:121 +#: src/view/com/threadgate/WhoCanReply.tsx:235 msgid "Everybody can reply" msgstr "所有人都可以回覆" -#: src/view/com/threadgate/WhoCanReply.tsx:129 -msgid "Everybody can reply." -msgstr "所有人都可以回覆。" - #: src/components/dms/MessagesNUX.tsx:131 #: src/components/dms/MessagesNUX.tsx:134 #: src/screens/Messages/Settings.tsx:75 @@ -2270,7 +2276,7 @@ msgstr "不適宜的圖像媒體" msgid "Handle" msgstr "帳號代碼" -#: src/view/screens/AccessibilitySettings.tsx:103 +#: src/view/screens/AccessibilitySettings.tsx:116 msgid "Haptics" msgstr "觸覺" @@ -2439,7 +2445,7 @@ msgstr "如果您想更改帳號代碼或電子郵件,請在停用帳號前更 msgid "Illegal and Urgent" msgstr "違法" -#: src/view/com/util/images/Gallery.tsx:39 +#: src/view/com/util/images/Gallery.tsx:42 msgid "Image" msgstr "圖片" @@ -2824,12 +2830,12 @@ msgstr "管理您靜音的文字和標籤" msgid "Mark as read" msgstr "標記為已讀" -#: src/view/screens/AccessibilitySettings.tsx:89 +#: src/view/screens/AccessibilitySettings.tsx:102 #: src/view/screens/Profile.tsx:195 msgid "Media" msgstr "媒體" -#: src/view/com/threadgate/WhoCanReply.tsx:183 +#: src/view/com/threadgate/WhoCanReply.tsx:270 msgid "mentioned users" msgstr "被提及的用戶" @@ -2938,7 +2944,7 @@ msgstr "內容管理工具" msgid "Moderator has chosen to set a general warning on the content." msgstr "內容管理者已將此內容標記為普通警告。" -#: src/view/com/post-thread/PostThreadItem.tsx:571 +#: src/view/com/post-thread/PostThreadItem.tsx:567 msgid "More" msgstr "更多" @@ -3305,7 +3311,7 @@ msgstr "暫時不需要" #: src/view/com/profile/ProfileMenu.tsx:373 #: src/view/com/util/forms/PostDropdownBtn.tsx:456 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:310 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:311 msgid "Note about sharing" msgstr "關於分享的注意事項" @@ -3393,9 +3399,9 @@ msgstr "至少有一張圖片缺失了替代文字。" msgid "Only .jpg and .png files are supported" msgstr "僅支援 .jpg 或 .png 格式的圖片" -#: src/view/com/threadgate/WhoCanReply.tsx:133 -msgid "Only {0} can reply." -msgstr "只有{0}可以回覆。" +#: src/view/com/threadgate/WhoCanReply.tsx:239 +msgid "Only {0} can reply" +msgstr "只有{0}可以回覆" #: src/screens/Signup/StepHandle.tsx:98 msgid "Only contains letters, numbers, and hyphens" @@ -3655,7 +3661,7 @@ msgstr "密碼已更新" msgid "Password updated!" msgstr "密碼已更新!" -#: src/view/com/util/post-embeds/GifEmbed.tsx:36 +#: src/view/com/util/post-embeds/GifEmbed.tsx:37 msgid "Pause" msgstr "暫停" @@ -3704,7 +3710,7 @@ msgstr "釘選的動態源列表" msgid "Pinned to your feeds" msgstr "從您的動態中取消釘選" -#: src/view/com/util/post-embeds/GifEmbed.tsx:36 +#: src/view/com/util/post-embeds/GifEmbed.tsx:37 msgid "Play" msgstr "播放" @@ -3712,7 +3718,7 @@ msgstr "播放" msgid "Play {0}" msgstr "播放 {0}" -#: src/view/com/util/post-embeds/GifEmbed.tsx:35 +#: src/view/com/util/post-embeds/GifEmbed.tsx:36 msgid "Play or pause the GIF" msgstr "播放或暫停 GIF" @@ -3955,10 +3961,10 @@ msgstr "發佈貼文" msgid "Publish reply" msgstr "發佈回覆" -#: src/view/com/util/post-ctrls/RepostButton.tsx:115 -#: src/view/com/util/post-ctrls/RepostButton.tsx:127 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:78 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:81 +#: src/view/com/util/post-ctrls/RepostButton.tsx:116 +#: src/view/com/util/post-ctrls/RepostButton.tsx:128 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:79 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:82 msgid "Quote post" msgstr "引用貼文" @@ -4063,8 +4069,8 @@ msgstr "刪除搜尋紀錄中的個人檔案" msgid "Remove quote" msgstr "刪除引用貼文" -#: src/view/com/util/post-ctrls/RepostButton.tsx:92 -#: src/view/com/util/post-ctrls/RepostButton.tsx:108 +#: src/view/com/util/post-ctrls/RepostButton.tsx:93 +#: src/view/com/util/post-ctrls/RepostButton.tsx:109 msgid "Remove repost" msgstr "刪除轉貼貼文" @@ -4104,9 +4110,17 @@ msgstr "用「Discover」動態源取代" msgid "Replies" msgstr "回覆" -#: src/view/com/threadgate/WhoCanReply.tsx:131 -msgid "Replies to this thread are disabled." -msgstr "對此討論串的回覆已停用。" +#: src/view/com/threadgate/WhoCanReply.tsx:66 +msgid "Replies disabled" +msgstr "回覆已被關閉" + +#: src/view/com/threadgate/WhoCanReply.tsx:123 +msgid "Replies on this thread are disabled" +msgstr "此討論串的回覆已停用" + +#: src/view/com/threadgate/WhoCanReply.tsx:237 +msgid "Replies to this thread are disabled" +msgstr "此討論串的回覆已停用。" #: src/view/com/composer/Composer.tsx:477 msgctxt "action" @@ -4193,21 +4207,21 @@ msgstr "檢舉這則貼文" msgid "Report this user" msgstr "檢舉這個用戶" -#: src/view/com/util/post-ctrls/RepostButton.tsx:64 -#: src/view/com/util/post-ctrls/RepostButton.tsx:93 -#: src/view/com/util/post-ctrls/RepostButton.tsx:109 +#: src/view/com/util/post-ctrls/RepostButton.tsx:65 +#: src/view/com/util/post-ctrls/RepostButton.tsx:94 +#: src/view/com/util/post-ctrls/RepostButton.tsx:110 msgctxt "action" msgid "Repost" msgstr "轉貼" -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:69 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:73 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:70 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:74 msgid "Repost" msgstr "轉貼" -#: src/view/com/util/post-ctrls/RepostButton.tsx:85 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:46 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:92 +#: src/view/com/util/post-ctrls/RepostButton.tsx:86 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:47 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:93 msgid "Repost or quote post" msgstr "轉貼或引用貼文" @@ -4241,7 +4255,7 @@ msgstr "請求變更" msgid "Request Code" msgstr "請求代碼" -#: src/view/screens/AccessibilitySettings.tsx:82 +#: src/view/screens/AccessibilitySettings.tsx:88 msgid "Require alt text before posting" msgstr "要求發佈前提供替代文字" @@ -4716,7 +4730,7 @@ msgstr "分享" #: src/view/com/profile/ProfileMenu.tsx:229 #: src/view/com/util/forms/PostDropdownBtn.tsx:307 #: src/view/com/util/forms/PostDropdownBtn.tsx:316 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:299 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:300 #: src/view/screens/ProfileList.tsx:428 msgid "Share" msgstr "分享" @@ -4731,7 +4745,7 @@ msgstr "分享一個趣聞!📰" #: src/view/com/profile/ProfileMenu.tsx:378 #: src/view/com/util/forms/PostDropdownBtn.tsx:461 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:315 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:316 msgid "Share anyway" msgstr "仍然分享" @@ -4760,7 +4774,7 @@ msgstr "分享網站的連結" msgid "Show" msgstr "顯示" -#: src/view/com/util/post-embeds/GifEmbed.tsx:167 +#: src/view/com/util/post-embeds/GifEmbed.tsx:169 msgid "Show alt text" msgstr "顯示替代文字" @@ -4791,7 +4805,7 @@ msgstr "顯示隱藏回覆" msgid "Show less like this" msgstr "減少顯示此類內容" -#: src/view/com/post-thread/PostThreadItem.tsx:537 +#: src/view/com/post-thread/PostThreadItem.tsx:533 #: src/view/com/post/Post.tsx:227 #: src/view/com/posts/FeedItem.tsx:396 msgid "Show More" @@ -4928,6 +4942,8 @@ msgid "Software Dev" msgstr "軟體開發" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 +#: src/view/com/threadgate/WhoCanReply.tsx:67 +#: src/view/com/threadgate/WhoCanReply.tsx:124 msgid "Some people can reply" msgstr "僅部分人可以回覆" @@ -5395,7 +5411,7 @@ msgid "This post has been deleted." msgstr "這則貼文已被刪除。" #: src/view/com/util/forms/PostDropdownBtn.tsx:458 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:312 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:313 msgid "This post is only visible to logged-in users. It won't be visible to people who aren't logged in." msgstr "只有登入用戶能見到這則貼文,未登入的人將看不到它。" @@ -5576,9 +5592,9 @@ msgstr "解除封鎖帳號" msgid "Unblock Account?" msgstr "解除封鎖?" -#: src/view/com/util/post-ctrls/RepostButton.tsx:63 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:69 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:73 +#: src/view/com/util/post-ctrls/RepostButton.tsx:64 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:70 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:74 msgid "Undo repost" msgstr "取消轉貼" @@ -5802,7 +5818,7 @@ msgstr "帳號代碼或電子郵件地址" msgid "Users" msgstr "用戶" -#: src/view/com/threadgate/WhoCanReply.tsx:187 +#: src/view/com/threadgate/WhoCanReply.tsx:274 msgid "users followed by <0/>" msgstr "被 <0/> 跟隨的用戶" @@ -6043,9 +6059,19 @@ msgid "Who can message you?" msgstr "誰可以傳送訊息給您?" #: src/view/com/modals/Threadgate.tsx:69 +#: src/view/com/threadgate/WhoCanReply.tsx:73 +#: src/view/com/threadgate/WhoCanReply.tsx:130 msgid "Who can reply" msgstr "誰可以回覆" +#: src/view/com/threadgate/WhoCanReply.tsx:206 +msgid "Who can reply dialog" +msgstr "「誰可以回覆」對話窗" + +#: src/view/com/threadgate/WhoCanReply.tsx:210 +msgid "Who can reply?" +msgstr "誰可以回覆?" + #: src/screens/Home/NoFeedsPinned.tsx:79 #: src/screens/Messages/List/index.tsx:185 msgid "Whoops!" From 6ac8389adba8bf7a0e235c6f2f1a96ab2d454340 Mon Sep 17 00:00:00 2001 From: Kuwa Lee Date: Thu, 20 Jun 2024 23:04:52 +0800 Subject: [PATCH 074/127] TW: hot fix --- src/locale/locales/zh-TW/messages.po | 12852 ++++++++++++------------- 1 file changed, 6426 insertions(+), 6426 deletions(-) diff --git a/src/locale/locales/zh-TW/messages.po b/src/locale/locales/zh-TW/messages.po index 37219cb613..808582d9c6 100644 --- a/src/locale/locales/zh-TW/messages.po +++ b/src/locale/locales/zh-TW/messages.po @@ -1,6426 +1,6426 @@ -msgid "" -msgstr "" -"Project-Id-Version: zh-TW for bluesky-social-app\n" -"POT-Creation-Date: \n" -"Report-Msgid-Bugs-To: Kuwa Lee , Frudrax Cheng \n" -"PO-Revision-Date: 2024-06-20 23:00+0800\n" -"Last-Translator: \n" -"Language-Team: Frudrax Cheng , Kuwa Lee , noeFly, snowleo208, Kisaragi Hiu, Yi-Jyun Pan, toto6038, cirx1e\n" -"Language: zh_TW\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Generator: @lingui/cli\n" -"Plural-Forms: \n" - -#: src/screens/Messages/List/ChatListItem.tsx:120 -msgid "(contains embedded content)" -msgstr "(含有嵌入內容)" - -#: src/view/com/modals/VerifyEmail.tsx:150 -msgid "(no email)" -msgstr "(沒有電子郵件)" - -#: src/view/com/notifications/FeedItem.tsx:263 -msgid "{0, plural, one {{formattedCount} other} other {{formattedCount} others}}" -msgstr "{0, plural, one {其他 {formattedCount} 個人} other {其他 {formattedCount} 個人}}" - -#: src/components/moderation/LabelsOnMe.tsx:55 -msgid "{0, plural, one {# label has been placed on this account} other {# labels have been placed on this account}}" -msgstr "{0, plural, one {該帳號有 # 個標記} other {該帳號有 # 個標記}}" - -#: src/components/moderation/LabelsOnMe.tsx:61 -msgid "{0, plural, one {# label has been placed on this content} other {# labels have been placed on this content}}" -msgstr "{0, plural, one {該內容有 # 個標記} other {該內容有 # 個標記}}" - -#: src/view/com/util/post-ctrls/RepostButton.tsx:66 -msgid "{0, plural, one {# repost} other {# reposts}}" -msgstr "{0, plural, one {# 個轉貼} other {# 個轉貼}}" - -#: src/components/ProfileHoverCard/index.web.tsx:398 -#: src/screens/Profile/Header/Metrics.tsx:23 -msgid "{0, plural, one {follower} other {followers}}" -msgstr "{0, plural, one {個跟隨者} other {個跟隨者}}" - -#: src/components/ProfileHoverCard/index.web.tsx:402 -#: src/screens/Profile/Header/Metrics.tsx:27 -msgid "{0, plural, one {following} other {following}}" -msgstr "{0, plural, one {個跟隨中} other {個跟隨中}}" - -#: src/view/com/util/post-ctrls/PostCtrls.tsx:255 -msgid "{0, plural, one {Like (# like)} other {Like (# likes)}}" -msgstr "{0, plural, one {喜歡(# 個喜歡)} other {喜歡(# 個喜歡)}}" - -#: src/view/com/post-thread/PostThreadItem.tsx:382 -msgid "{0, plural, one {like} other {likes}}" -msgstr "{0, plural, one {喜歡} other {喜歡}}" - -#: src/components/FeedCard.tsx:111 -#: src/view/com/feeds/FeedSourceCard.tsx:301 -msgid "{0, plural, one {Liked by # user} other {Liked by # users}}" -msgstr "{0, plural,one {# 個用戶已喜歡} other {# 個用戶已喜歡}}" - -#: src/screens/Profile/Header/Metrics.tsx:59 -msgid "{0, plural, one {post} other {posts}}" -msgstr "{0, plural, one {則貼文} other {則貼文}}" - -#: src/view/com/util/post-ctrls/PostCtrls.tsx:213 -msgid "{0, plural, one {Reply (# reply)} other {Reply (# replies)}}" -msgstr "{0, plural, one {回覆(# 個回覆)} other {回覆(# 個回覆)}}" - -#: src/view/com/post-thread/PostThreadItem.tsx:362 -msgid "{0, plural, one {repost} other {reposts}}" -msgstr "{0, plural, one {轉貼} other {轉貼}}" - -#: src/view/com/util/post-ctrls/PostCtrls.tsx:251 -msgid "{0, plural, one {Unlike (# like)} other {Unlike (# likes)}}" -msgstr "{0, plural, one {撤回喜歡(# 個喜歡)} other {撤回喜歡(# 個喜歡)}}" - -#: src/view/com/util/UserAvatar.tsx:419 -msgid "{0}'s avatar" -msgstr "{0} 的頭像" - -#: src/components/LabelingServiceCard/index.tsx:71 -msgid "{count, plural, one {Liked by # user} other {Liked by # users}}" -msgstr "{count, plural, one {# 個用戶已喜歡} other {# 個用戶已喜歡}}" - -#: src/lib/hooks/useTimeAgo.ts:69 -msgid "{diff, plural, one {day} other {days}}" -msgstr "{diff, plural, one {天} other {天}}" - -#: src/lib/hooks/useTimeAgo.ts:64 -msgid "{diff, plural, one {hour} other {hours}}" -msgstr "{diff, plural, one {時} other {時}}" - -#: src/lib/hooks/useTimeAgo.ts:59 -msgid "{diff, plural, one {minute} other {minutes}}" -msgstr "{diff, plural, one {分} other {分}}" - -#: src/lib/hooks/useTimeAgo.ts:75 -msgid "{diff, plural, one {month} other {months}}" -msgstr "{diff, plural, one {月} other {月}}" - -#: src/lib/hooks/useTimeAgo.ts:54 -msgid "{diffSeconds, plural, one {second} other {seconds}}" -msgstr "{diffSeconds, plural, one {秒} other {秒}}" - -#: src/screens/SignupQueued.tsx:207 -msgid "{estimatedTimeHrs, plural, one {hour} other {hours}}" -msgstr "{estimatedTimeHrs, plural, one {時} other {時}}" - -#: src/screens/SignupQueued.tsx:213 -msgid "{estimatedTimeMins, plural, one {minute} other {minutes}}" -msgstr "{estimatedTimeMins, plural, one {分} other {分}}" - -#: src/components/ProfileHoverCard/index.web.tsx:503 -#: src/screens/Profile/Header/Metrics.tsx:50 -msgid "{following} following" -msgstr "{following} 個跟隨中" - -#: src/components/dms/dialogs/SearchablePeopleList.tsx:405 -msgid "{handle} can't be messaged" -msgstr "無法傳送訊息給 {handle}" - -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:286 -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:299 -#: src/view/screens/ProfileFeed.tsx:585 -msgid "{likeCount, plural, one {Liked by # user} other {Liked by # users}}" -msgstr "{likeCount, plural, one {# 個用戶已喜歡} other {# 個用戶已喜歡}}" - -#: src/view/shell/Drawer.tsx:462 -msgid "{numUnreadNotifications} unread" -msgstr "{numUnreadNotifications} 個未讀通知" - -#: src/components/NewskieDialog.tsx:75 -msgid "{profileName} joined Bluesky {0} ago" -msgstr "{profileName} 在 {0} 前加入了 Bluesky" - -#: src/view/screens/PreferencesFollowingFeed.tsx:67 -msgid "{value, plural, =0 {Show all replies} one {Show replies with at least # like} other {Show replies with at least # likes}}" -msgstr "{value, plural, =0 {顯示所有回覆} one {顯示至少 # 個喜歡的回覆} other {顯示至少 # 個喜歡的回覆}}" - -#: src/view/com/threadgate/WhoCanReply.tsx:290 -msgid "<0/> members" -msgstr "<0/> 個成員" - -#: src/view/shell/Drawer.tsx:101 -msgid "<0>{0} {1, plural, one {follower} other {followers}}" -msgstr "<0>{0} {1, plural, one {個跟隨者} other {個跟隨者}}" - -#: src/view/shell/Drawer.tsx:112 -msgid "<0>{0} {1, plural, one {following} other {following}}" -msgstr "<0>{0} {1, plural, one {個跟隨中} other {個跟隨中}}" - -#: src/view/com/modals/SelfLabel.tsx:135 -msgid "<0>Not Applicable. This warning is only available for posts with media attached." -msgstr "<0>不適用。 此警告只適用於附帶媒體的貼文。" - -#: src/screens/Profile/Header/Handle.tsx:50 -msgid "⚠Invalid Handle" -msgstr "⚠無效的帳號代碼" - -#: src/screens/Login/LoginForm.tsx:244 -msgid "2FA Confirmation" -msgstr "雙重驗證" - -#: src/view/com/util/ViewHeader.tsx:93 -#: src/view/screens/Search/Search.tsx:684 -msgid "Access navigation links and settings" -msgstr "存取導覽連結和設定" - -#: src/view/com/home/HomeHeaderLayoutMobile.tsx:56 -msgid "Access profile and other navigation links" -msgstr "存取個人檔案和其他導覽連結" - -#: src/view/com/modals/EditImage.tsx:300 -#: src/view/screens/Settings/index.tsx:518 -msgid "Accessibility" -msgstr "無障礙" - -#: src/view/screens/Settings/index.tsx:509 -msgid "Accessibility settings" -msgstr "無障礙設定" - -#: src/Navigation.tsx:296 -#: src/view/screens/AccessibilitySettings.tsx:69 -msgid "Accessibility Settings" -msgstr "無障礙設定" - -#: src/screens/Login/LoginForm.tsx:167 -#: src/view/screens/Settings/index.tsx:345 -#: src/view/screens/Settings/index.tsx:752 -msgid "Account" -msgstr "帳號" - -#: src/view/com/profile/ProfileMenu.tsx:145 -msgid "Account blocked" -msgstr "已封鎖帳號" - -#: src/view/com/profile/ProfileMenu.tsx:159 -msgid "Account followed" -msgstr "已跟隨帳號" - -#: src/view/com/profile/ProfileMenu.tsx:119 -msgid "Account muted" -msgstr "已靜音帳號" - -#: src/components/moderation/ModerationDetailsDialog.tsx:93 -#: src/lib/moderation/useModerationCauseDescription.ts:93 -msgid "Account Muted" -msgstr "已靜音帳號" - -#: src/components/moderation/ModerationDetailsDialog.tsx:82 -msgid "Account Muted by List" -msgstr "帳號已被列表靜音" - -#: src/view/com/util/AccountDropdownBtn.tsx:41 -msgid "Account options" -msgstr "帳號選項" - -#: src/view/com/util/AccountDropdownBtn.tsx:25 -msgid "Account removed from quick access" -msgstr "已從快速存取中移除帳號" - -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:139 -#: src/view/com/profile/ProfileMenu.tsx:134 -msgid "Account unblocked" -msgstr "已解除封鎖帳號" - -#: src/view/com/profile/ProfileMenu.tsx:172 -msgid "Account unfollowed" -msgstr "已取消跟隨帳號" - -#: src/view/com/profile/ProfileMenu.tsx:108 -msgid "Account unmuted" -msgstr "已取消靜音帳號" - -#: src/components/dialogs/MutedWords.tsx:164 -#: src/view/com/modals/ListAddRemoveUsers.tsx:268 -#: src/view/com/modals/UserAddRemoveLists.tsx:230 -#: src/view/screens/ProfileList.tsx:881 -msgid "Add" -msgstr "新增" - -#: src/view/com/modals/SelfLabel.tsx:57 -msgid "Add a content warning" -msgstr "新增內容警告" - -#: src/view/screens/ProfileList.tsx:871 -msgid "Add a user to this list" -msgstr "將用戶新增至此列表" - -#: src/components/dialogs/SwitchAccount.tsx:56 -#: src/screens/Deactivated.tsx:199 -#: src/view/screens/Settings/index.tsx:422 -#: src/view/screens/Settings/index.tsx:431 -msgid "Add account" -msgstr "新增帳號" - -#: src/view/com/composer/GifAltText.tsx:69 -#: src/view/com/composer/GifAltText.tsx:135 -#: src/view/com/composer/GifAltText.tsx:175 -#: src/view/com/composer/photos/Gallery.tsx:120 -#: src/view/com/composer/photos/Gallery.tsx:187 -#: src/view/com/modals/AltImage.tsx:118 -msgid "Add alt text" -msgstr "新增替代文字" - -#: src/view/screens/AppPasswords.tsx:106 -#: src/view/screens/AppPasswords.tsx:148 -#: src/view/screens/AppPasswords.tsx:161 -msgid "Add App Password" -msgstr "新增應用程式專用密碼" - -#: src/components/dialogs/MutedWords.tsx:157 -msgid "Add mute word for configured settings" -msgstr "在已配置的設定中新增靜音文字" - -#: src/components/dialogs/MutedWords.tsx:86 -msgid "Add muted words and tags" -msgstr "新增靜音文字及標籤" - -#: src/screens/Home/NoFeedsPinned.tsx:99 -msgid "Add recommended feeds" -msgstr "新增推薦的動態源" - -#: src/screens/Feeds/NoFollowingFeed.tsx:41 -msgid "Add the default feed of only people you follow" -msgstr "新增預設的「Following」動態源,它只會顯示您跟隨的人" - -#: src/view/com/modals/ChangeHandle.tsx:410 -msgid "Add the following DNS record to your domain:" -msgstr "將以下 DNS 記錄新增到您的網域:" - -#: src/components/FeedCard.tsx:180 -msgid "Add this feed to your feeds" -msgstr "將此新增至您的動態源" - -#: src/view/com/profile/ProfileMenu.tsx:268 -#: src/view/com/profile/ProfileMenu.tsx:271 -msgid "Add to Lists" -msgstr "新增至列表" - -#: src/view/com/feeds/FeedSourceCard.tsx:267 -msgid "Add to my feeds" -msgstr "加入到我的動態源" - -#: src/view/com/modals/ListAddRemoveUsers.tsx:191 -#: src/view/com/modals/UserAddRemoveLists.tsx:157 -msgid "Added to list" -msgstr "新增至列表" - -#: src/view/com/feeds/FeedSourceCard.tsx:126 -msgid "Added to my feeds" -msgstr "加入到我的動態源" - -#: src/view/screens/PreferencesFollowingFeed.tsx:172 -msgid "Adjust the number of likes a reply must have to be shown in your feed." -msgstr "調整回覆貼文在您的動態中顯示所需的最低喜歡數量。" - -#: src/lib/moderation/useGlobalLabelStrings.ts:34 -#: src/view/com/modals/SelfLabel.tsx:76 -msgid "Adult Content" -msgstr "成人內容" - -#: src/components/moderation/LabelPreference.tsx:242 -msgid "Adult content is disabled." -msgstr "成人內容已停用。" - -#: src/screens/Moderation/index.tsx:375 -#: src/view/screens/Settings/index.tsx:686 -msgid "Advanced" -msgstr "進階設定" - -#: src/view/screens/Feeds.tsx:737 -msgid "All the feeds you've saved, right in one place." -msgstr "以下是您儲存的動態源。" - -#: src/view/com/modals/AddAppPasswords.tsx:187 -#: src/view/com/modals/AddAppPasswords.tsx:194 -msgid "Allow access to your direct messages" -msgstr "允許存取您的私人訊息" - -#: src/screens/Messages/Settings.tsx:62 -#: src/screens/Messages/Settings.tsx:65 -msgid "Allow new messages from" -msgstr "允許這些人向您發起對話:" - -#: src/screens/Login/ForgotPasswordForm.tsx:178 -#: src/view/com/modals/ChangePassword.tsx:171 -msgid "Already have a code?" -msgstr "已經有重置碼了?" - -#: src/screens/Login/ChooseAccountForm.tsx:49 -msgid "Already signed in as @{0}" -msgstr "已以 @{0} 身份登入" - -#: src/view/com/composer/GifAltText.tsx:93 -#: src/view/com/composer/photos/Gallery.tsx:144 -#: src/view/com/util/post-embeds/GifEmbed.tsx:177 -msgid "ALT" -msgstr "ALT" - -#: src/view/com/composer/GifAltText.tsx:144 -#: src/view/com/modals/EditImage.tsx:316 -#: src/view/screens/AccessibilitySettings.tsx:83 -msgid "Alt text" -msgstr "替代文字" - -#: src/view/com/util/post-embeds/GifEmbed.tsx:183 -msgid "Alt Text" -msgstr "替代文字" - -#: src/view/com/composer/photos/Gallery.tsx:224 -msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone." -msgstr "替代文字為盲人和視障人士描述圖片及提供情境。" - -#: src/view/com/modals/VerifyEmail.tsx:132 -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:96 -msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below." -msgstr "一封電子郵件已發送至 {0}。請查閱郵件並在下方輸入驗證碼。" - -#: src/view/com/modals/ChangeEmail.tsx:114 -msgid "An email has been sent to your previous address, {0}. It includes a confirmation code which you can enter below." -msgstr "一封電子郵件已發送至先前填寫的電子郵件地址 {0}。請查閱郵件並在下方輸入驗證碼。" - -#: src/components/dialogs/GifSelect.tsx:252 -msgid "An error occured" -msgstr "發生錯誤" - -#: src/lib/moderation/useReportOptions.ts:27 -msgid "An issue not included in these options" -msgstr "問題不在上述選項" - -#: src/components/hooks/useFollowMethods.ts:35 -#: src/components/hooks/useFollowMethods.ts:50 -#: src/view/com/profile/FollowButton.tsx:35 -#: src/view/com/profile/FollowButton.tsx:45 -#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:188 -#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:198 -msgid "An issue occurred, please try again." -msgstr "出現問題,請再試一次。" - -#: src/screens/Onboarding/StepInterests/index.tsx:194 -msgid "an unknown error occurred" -msgstr "出現未知錯誤" - -#: src/view/com/notifications/FeedItem.tsx:260 -#: src/view/com/threadgate/WhoCanReply.tsx:311 -msgid "and" -msgstr "和" - -#: src/screens/Onboarding/index.tsx:29 -msgid "Animals" -msgstr "動物" - -#: src/view/com/util/post-embeds/GifEmbed.tsx:149 -msgid "Animated GIF" -msgstr "GIF 動畫" - -#: src/lib/moderation/useReportOptions.ts:32 -msgid "Anti-Social Behavior" -msgstr "反社會行為" - -#: src/view/screens/LanguageSettings.tsx:96 -msgid "App Language" -msgstr "應用程式語言" - -#: src/view/screens/AppPasswords.tsx:228 -msgid "App password deleted" -msgstr "應用程式專用密碼已刪除" - -#: src/view/com/modals/AddAppPasswords.tsx:138 -msgid "App Password names can only contain letters, numbers, spaces, dashes, and underscores." -msgstr "應用程式專用密碼只能包含字母、數字、空格、破折號及底線。" - -#: src/view/com/modals/AddAppPasswords.tsx:103 -msgid "App Password names must be at least 4 characters long." -msgstr "應用程式專用密碼名稱必須至少為 4 個字元。" - -#: src/view/screens/Settings/index.tsx:697 -msgid "App password settings" -msgstr "應用程式專用密碼設定" - -#: src/Navigation.tsx:264 -#: src/view/screens/AppPasswords.tsx:192 -#: src/view/screens/Settings/index.tsx:706 -msgid "App Passwords" -msgstr "應用程式專用密碼" - -#: src/components/moderation/LabelsOnMeDialog.tsx:151 -#: src/components/moderation/LabelsOnMeDialog.tsx:154 -msgid "Appeal" -msgstr "申訴" - -#: src/components/moderation/LabelsOnMeDialog.tsx:236 -msgid "Appeal \"{0}\" label" -msgstr "申訴「{0}」標記" - -#: src/components/moderation/LabelsOnMeDialog.tsx:227 -#: src/screens/Messages/Conversation/ChatDisabled.tsx:91 -msgid "Appeal submitted" -msgstr "已提交申訴" - -#: src/screens/Messages/Conversation/ChatDisabled.tsx:51 -#: src/screens/Messages/Conversation/ChatDisabled.tsx:53 -#: src/screens/Messages/Conversation/ChatDisabled.tsx:99 -#: src/screens/Messages/Conversation/ChatDisabled.tsx:101 -msgid "Appeal this decision" -msgstr "對此決定提出上訴" - -#: src/view/screens/Settings/index.tsx:439 -msgid "Appearance" -msgstr "外觀" - -#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:47 -#: src/screens/Home/NoFeedsPinned.tsx:93 -msgid "Apply default recommended feeds" -msgstr "使用預設推薦的動態源" - -#: src/view/screens/AppPasswords.tsx:282 -msgid "Are you sure you want to delete the app password \"{name}\"?" -msgstr "您確定要刪除這個應用程式專用密碼「{name}」嗎?" - -#: src/components/dms/MessageMenu.tsx:149 -msgid "Are you sure you want to delete this message? The message will be deleted for you, but not for the other participant." -msgstr "您確定要刪除這則訊息嗎?該訊息將為您刪除,但不會為其他參與者刪除。" - -#: src/components/dms/LeaveConvoPrompt.tsx:48 -msgid "Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for the other participant." -msgstr "您確定要離開此對話嗎?您的訊息將為您刪除,但不會為其他參與者刪除。" - -#: src/view/com/feeds/FeedSourceCard.tsx:314 -msgid "Are you sure you want to remove {0} from your feeds?" -msgstr "您確定要從您的動態中移除 {0} 嗎?" - -#: src/components/FeedCard.tsx:197 -msgid "Are you sure you want to remove this from your feeds?" -msgstr "您確定要將此從您的動態源中移除嗎?" - -#: src/view/com/composer/Composer.tsx:632 -msgid "Are you sure you'd like to discard this draft?" -msgstr "您確定要捨棄此草稿嗎?" - -#: src/components/dialogs/MutedWords.tsx:281 -msgid "Are you sure?" -msgstr "您確定嗎?" - -#: src/view/com/composer/select-language/SuggestedLanguage.tsx:60 -msgid "Are you writing in <0>{0}?" -msgstr "您正在使用 <0>{0} 書寫嗎?" - -#: src/screens/Onboarding/index.tsx:23 -msgid "Art" -msgstr "藝術" - -#: src/view/com/modals/SelfLabel.tsx:124 -msgid "Artistic or non-erotic nudity." -msgstr "藝術作品或非色情的裸露。" - -#: src/screens/Signup/StepHandle.tsx:119 -msgid "At least 3 characters" -msgstr "至少 3 個字元" - -#: src/components/dms/MessagesListHeader.tsx:75 -#: src/components/moderation/LabelsOnMeDialog.tsx:281 -#: src/components/moderation/LabelsOnMeDialog.tsx:282 -#: src/screens/Login/ChooseAccountForm.tsx:98 -#: src/screens/Login/ChooseAccountForm.tsx:103 -#: src/screens/Login/ForgotPasswordForm.tsx:129 -#: src/screens/Login/ForgotPasswordForm.tsx:135 -#: src/screens/Login/LoginForm.tsx:275 -#: src/screens/Login/LoginForm.tsx:281 -#: src/screens/Login/SetNewPasswordForm.tsx:160 -#: src/screens/Login/SetNewPasswordForm.tsx:166 -#: src/screens/Messages/Conversation/ChatDisabled.tsx:133 -#: src/screens/Messages/Conversation/ChatDisabled.tsx:134 -#: src/screens/Profile/Header/Shell.tsx:102 -#: src/screens/Signup/index.tsx:193 -#: src/view/com/util/ViewHeader.tsx:91 -msgid "Back" -msgstr "返回" - -#: src/view/screens/Settings/index.tsx:496 -msgid "Basics" -msgstr "基本設定" - -#: src/components/dialogs/BirthDateSettings.tsx:107 -msgid "Birthday" -msgstr "生日" - -#: src/view/screens/Settings/index.tsx:377 -msgid "Birthday:" -msgstr "生日:" - -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:314 -#: src/view/com/profile/ProfileMenu.tsx:366 -msgid "Block" -msgstr "封鎖" - -#: src/components/dms/ConvoMenu.tsx:188 -#: src/components/dms/ConvoMenu.tsx:192 -msgid "Block account" -msgstr "封鎖帳號" - -#: src/view/com/profile/ProfileMenu.tsx:305 -#: src/view/com/profile/ProfileMenu.tsx:312 -msgid "Block Account" -msgstr "封鎖帳號" - -#: src/view/com/profile/ProfileMenu.tsx:349 -msgid "Block Account?" -msgstr "封鎖帳號?" - -#: src/view/screens/ProfileList.tsx:584 -msgid "Block accounts" -msgstr "封鎖帳號" - -#: src/view/screens/ProfileList.tsx:688 -msgid "Block list" -msgstr "封鎖列表" - -#: src/view/screens/ProfileList.tsx:683 -msgid "Block these accounts?" -msgstr "封鎖這些帳號?" - -#: src/view/com/lists/ListCard.tsx:112 -#: src/view/com/util/post-embeds/QuoteEmbed.tsx:75 -msgid "Blocked" -msgstr "已被封鎖" - -#: src/screens/Moderation/index.tsx:267 -msgid "Blocked accounts" -msgstr "已封鎖帳號" - -#: src/Navigation.tsx:140 -#: src/view/screens/ModerationBlockedAccounts.tsx:109 -msgid "Blocked Accounts" -msgstr "已封鎖帳號" - -#: src/view/com/profile/ProfileMenu.tsx:361 -msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." -msgstr "被封鎖的帳號無法在您的討論串中回覆、提及您,或以其他方式與您互動。" - -#: src/view/screens/ModerationBlockedAccounts.tsx:117 -msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours." -msgstr "被封鎖的帳號無法在您的討論串中回覆、提及您,或以其他方式與您互動。您將看不到他們的內容,他們也會被阻止看到您的內容。" - -#: src/view/com/post-thread/PostThread.tsx:367 -msgid "Blocked post." -msgstr "已封鎖貼文。" - -#: src/screens/Profile/Sections/Labels.tsx:173 -msgid "Blocking does not prevent this labeler from placing labels on your account." -msgstr "封鎖此帳號不會阻止被貼上標記。" - -#: src/view/screens/ProfileList.tsx:685 -msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." -msgstr "封鎖資訊是公開的。被封鎖的帳號無法在您的討論串中回覆、提及您,或以其他方式與您互動。" - -#: src/view/com/profile/ProfileMenu.tsx:358 -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 "封鎖此帳號不會阻止被貼上標記,但它會阻止此帳號在您的討論串中回覆或與您進行互動。" - -#: src/view/com/auth/SplashScreen.web.tsx:154 -msgid "Blog" -msgstr "部落格" - -#: src/view/com/auth/server-input/index.tsx:89 -#: src/view/com/auth/server-input/index.tsx:91 -msgid "Bluesky" -msgstr "Bluesky" - -#: src/view/com/auth/server-input/index.tsx:154 -msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers." -msgstr "Bluesky 是一個開放的網路,您可以自行挑選託管服務供應商。自定義託管服務現已為開發人員推出測試版。" - -#: src/screens/Moderation/index.tsx:533 -msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private." -msgstr "Bluesky 的官方程式將不會向未登入的使用者顯示您的個人檔案和貼文。但其他應用程式可能不會遵循這個要求,這不會使您的帳號變成非公開的。" - -#: src/lib/moderation/useLabelBehaviorDescription.ts:53 -msgid "Blur images" -msgstr "模糊圖片" - -#: src/lib/moderation/useLabelBehaviorDescription.ts:51 -msgid "Blur images and filter from feeds" -msgstr "模糊圖片並從動態中過濾" - -#: src/screens/Onboarding/index.tsx:30 -msgid "Books" -msgstr "書籍" - -#: src/screens/Home/NoFeedsPinned.tsx:103 -#: src/screens/Home/NoFeedsPinned.tsx:109 -msgid "Browse other feeds" -msgstr "瀏覽其他動態源" - -#: src/view/com/auth/SplashScreen.web.tsx:151 -msgid "Business" -msgstr "商務" - -#: src/view/com/profile/ProfileSubpageHeader.tsx:156 -msgid "by —" -msgstr "來自 —" - -#: src/components/LabelingServiceCard/index.tsx:56 -msgid "By {0}" -msgstr "來自 {0}" - -#: src/view/com/profile/ProfileSubpageHeader.tsx:160 -msgid "by <0/>" -msgstr "來自 <0/>" - -#: src/screens/Signup/StepInfo/Policies.tsx:74 -msgid "By creating an account you agree to the {els}." -msgstr "建立帳號即表示您同意 {els}。" - -#: src/view/com/profile/ProfileSubpageHeader.tsx:158 -msgid "by you" -msgstr "來自您" - -#: src/view/com/composer/photos/OpenCameraBtn.tsx:73 -msgid "Camera" -msgstr "相機" - -#: src/view/com/modals/AddAppPasswords.tsx:179 -msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long." -msgstr "只能包含字母、數字、空格、破折號及底線。長度必須至少 4 個字元,但不超過 32 個字元。" - -#: src/components/Menu/index.tsx:215 -#: src/components/Prompt.tsx:119 -#: src/components/Prompt.tsx:121 -#: src/components/TagMenu/index.tsx:268 -#: src/screens/Deactivated.tsx:161 -#: src/view/com/composer/Composer.tsx:434 -#: src/view/com/composer/Composer.tsx:440 -#: src/view/com/modals/ChangeEmail.tsx:213 -#: src/view/com/modals/ChangeEmail.tsx:215 -#: src/view/com/modals/ChangeHandle.tsx:148 -#: src/view/com/modals/ChangePassword.tsx:268 -#: src/view/com/modals/ChangePassword.tsx:271 -#: src/view/com/modals/CreateOrEditList.tsx:344 -#: src/view/com/modals/crop-image/CropImage.web.tsx:162 -#: src/view/com/modals/EditImage.tsx:324 -#: src/view/com/modals/EditProfile.tsx:250 -#: src/view/com/modals/InAppBrowserConsent.tsx:78 -#: src/view/com/modals/InAppBrowserConsent.tsx:80 -#: src/view/com/modals/LinkWarning.tsx:105 -#: src/view/com/modals/LinkWarning.tsx:107 -#: src/view/com/modals/VerifyEmail.tsx:255 -#: src/view/com/modals/VerifyEmail.tsx:261 -#: src/view/com/util/post-ctrls/RepostButton.tsx:139 -#: src/view/screens/Search/Search.tsx:704 -#: src/view/shell/desktop/Search.tsx:218 -msgid "Cancel" -msgstr "取消" - -#: src/view/com/modals/CreateOrEditList.tsx:349 -#: src/view/com/modals/DeleteAccount.tsx:174 -#: src/view/com/modals/DeleteAccount.tsx:296 -msgctxt "action" -msgid "Cancel" -msgstr "取消" - -#: src/view/com/modals/DeleteAccount.tsx:170 -#: src/view/com/modals/DeleteAccount.tsx:292 -msgid "Cancel account deletion" -msgstr "取消刪除帳號" - -#: src/view/com/modals/ChangeHandle.tsx:144 -msgid "Cancel change handle" -msgstr "取消修改帳號代碼" - -#: src/view/com/modals/crop-image/CropImage.web.tsx:159 -msgid "Cancel image crop" -msgstr "取消圖片裁剪" - -#: src/view/com/modals/EditProfile.tsx:245 -msgid "Cancel profile editing" -msgstr "取消編輯個人檔案" - -#: src/view/com/util/post-ctrls/RepostButton.tsx:133 -msgid "Cancel quote post" -msgstr "取消引用貼文" - -#: src/screens/Deactivated.tsx:155 -msgid "Cancel reactivation and log out" -msgstr "取消重新啟用並登出" - -#: src/view/com/modals/ListAddRemoveUsers.tsx:87 -#: src/view/shell/desktop/Search.tsx:214 -msgid "Cancel search" -msgstr "取消搜尋" - -#: src/view/com/modals/LinkWarning.tsx:106 -msgid "Cancels opening the linked website" -msgstr "取消開啟網站連結" - -#: src/view/com/modals/VerifyEmail.tsx:160 -msgid "Change" -msgstr "變更" - -#: src/view/screens/Settings/index.tsx:371 -msgctxt "action" -msgid "Change" -msgstr "變更" - -#: src/view/screens/Settings/index.tsx:718 -msgid "Change handle" -msgstr "變更帳號代碼" - -#: src/view/com/modals/ChangeHandle.tsx:156 -#: src/view/screens/Settings/index.tsx:729 -msgid "Change Handle" -msgstr "變更帳號代碼" - -#: src/view/com/modals/VerifyEmail.tsx:155 -msgid "Change my email" -msgstr "變更我的電子郵件地址" - -#: src/view/screens/Settings/index.tsx:763 -msgid "Change password" -msgstr "變更密碼" - -#: src/view/com/modals/ChangePassword.tsx:142 -#: src/view/screens/Settings/index.tsx:774 -msgid "Change Password" -msgstr "變更密碼" - -#: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 -msgid "Change post language to {0}" -msgstr "變更貼文的發佈語言為 {0}" - -#: src/view/com/modals/ChangeEmail.tsx:104 -msgid "Change Your Email" -msgstr "變更您的電子郵件地址" - -#: src/Navigation.tsx:308 -#: src/view/shell/bottom-bar/BottomBar.tsx:201 -#: src/view/shell/desktop/LeftNav.tsx:295 -msgid "Chat" -msgstr "對話" - -#: src/components/dms/ConvoMenu.tsx:82 -msgid "Chat muted" -msgstr "對話已靜音" - -#: src/components/dms/ConvoMenu.tsx:112 -#: src/components/dms/MessageMenu.tsx:81 -#: src/Navigation.tsx:313 -#: src/screens/Messages/List/index.tsx:88 -#: src/view/screens/Settings/index.tsx:638 -msgid "Chat settings" -msgstr "對話設定" - -#: src/screens/Messages/Settings.tsx:59 -#: src/view/screens/Settings/index.tsx:647 -msgid "Chat Settings" -msgstr "對話設定" - -#: src/components/dms/ConvoMenu.tsx:84 -msgid "Chat unmuted" -msgstr "對話已解除靜音" - -#: src/screens/SignupQueued.tsx:78 -#: src/screens/SignupQueued.tsx:82 -msgid "Check my status" -msgstr "檢查我的狀態" - -#: src/screens/Login/LoginForm.tsx:268 -msgid "Check your email for a login code and enter it here." -msgstr "在此輸入寄送至您電子郵件地址的驗證碼。" - -#: src/view/com/modals/DeleteAccount.tsx:231 -msgid "Check your inbox for an email with the confirmation code to enter below:" -msgstr "在下方輸入寄送至您電子郵件地址的驗證碼:" - -#: src/view/com/modals/Threadgate.tsx:75 -msgid "Choose \"Everybody\" or \"Nobody\"" -msgstr "選擇「所有人」或「沒有人」" - -#: src/view/com/auth/server-input/index.tsx:79 -msgid "Choose Service" -msgstr "選擇服務" - -#: src/screens/Onboarding/StepFinished.tsx:168 -msgid "Choose the algorithms that power your custom feeds." -msgstr "選擇提供您自定義動態的演算法。" - -#: src/screens/Onboarding/StepProfile/AvatarCreatorItems.tsx:107 -msgid "Choose this color as your avatar" -msgstr "選擇這個顏色作為您的頭像" - -#: src/screens/Signup/StepInfo/index.tsx:114 -msgid "Choose your password" -msgstr "選擇您的密碼" - -#: src/view/screens/Settings/index.tsx:910 -msgid "Clear all legacy storage data" -msgstr "清除所有遺留資料" - -#: src/view/screens/Settings/index.tsx:913 -msgid "Clear all legacy storage data (restart after this)" -msgstr "清除所有遺留資料(並重啟)" - -#: src/view/screens/Settings/index.tsx:922 -msgid "Clear all storage data" -msgstr "清除所有資料" - -#: src/view/screens/Settings/index.tsx:925 -msgid "Clear all storage data (restart after this)" -msgstr "清除所有資料(並重啟)" - -#: src/view/com/util/forms/SearchInput.tsx:88 -#: src/view/screens/Search/Search.tsx:824 -msgid "Clear search query" -msgstr "清除搜尋記錄" - -#: src/view/screens/Settings/index.tsx:911 -msgid "Clears all legacy storage data" -msgstr "清除所有遺留資料" - -#: src/view/screens/Settings/index.tsx:923 -msgid "Clears all storage data" -msgstr "清除所有資料" - -#: src/view/screens/Support.tsx:40 -msgid "click here" -msgstr "點擊這裡" - -#: src/view/com/modals/DeleteAccount.tsx:208 -msgid "Click here for more information on deactivating your account" -msgstr "點擊這裡以瞭解有關停用帳號的詳細資訊" - -#: src/view/com/modals/DeleteAccount.tsx:216 -msgid "Click here for more information." -msgstr "點擊這裡以瞭解更多資訊。" - -#: src/components/TagMenu/index.web.tsx:138 -msgid "Click here to open tag menu for {tag}" -msgstr "點擊這裡以開啟 {tag} 的標籤選單" - -#: src/components/dms/MessageItem.tsx:237 -msgid "Click to retry failed message" -msgstr "點擊以重試傳送訊息" - -#: src/screens/Onboarding/index.tsx:32 -msgid "Climate" -msgstr "氣象" - -#: src/components/dms/ChatEmptyPill.tsx:39 -msgid "Clip 🐴 clop 🐴" -msgstr "達達的馬蹄🐴是美麗的錯誤🐴" - -#: src/components/dialogs/GifSelect.ios.tsx:250 -#: src/components/dialogs/GifSelect.tsx:268 -#: src/components/dms/dialogs/SearchablePeopleList.tsx:261 -#: src/view/com/modals/ChangePassword.tsx:268 -#: src/view/com/modals/ChangePassword.tsx:271 -#: src/view/com/util/post-embeds/GifEmbed.tsx:189 -msgid "Close" -msgstr "關閉" - -#: src/components/Dialog/index.web.tsx:113 -#: src/components/Dialog/index.web.tsx:251 -msgid "Close active dialog" -msgstr "關閉打開的對話框" - -#: src/screens/Login/PasswordUpdatedForm.tsx:38 -msgid "Close alert" -msgstr "關閉警告" - -#: src/view/com/util/BottomSheetCustomBackdrop.tsx:36 -msgid "Close bottom drawer" -msgstr "關閉底欄" - -#: src/components/dialogs/GifSelect.ios.tsx:244 -#: src/components/dialogs/GifSelect.tsx:262 -msgid "Close dialog" -msgstr "關閉對話框" - -#: src/components/dialogs/GifSelect.tsx:161 -msgid "Close GIF dialog" -msgstr "關閉 GIF 對話框" - -#: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:36 -msgid "Close image" -msgstr "關閉圖片" - -#: src/view/com/lightbox/Lightbox.web.tsx:129 -msgid "Close image viewer" -msgstr "關閉圖片檢視器" - -#: src/components/dms/MessagesNUX.tsx:162 -msgid "Close modal" -msgstr "關閉視窗" - -#: src/view/shell/index.web.tsx:61 -msgid "Close navigation footer" -msgstr "關閉導覽頁腳" - -#: src/components/Menu/index.tsx:209 -#: src/components/TagMenu/index.tsx:262 -msgid "Close this dialog" -msgstr "關閉此對話框" - -#: src/view/shell/index.web.tsx:62 -msgid "Closes bottom navigation bar" -msgstr "關閉底部導覽列" - -#: src/screens/Login/PasswordUpdatedForm.tsx:39 -msgid "Closes password update alert" -msgstr "關閉密碼更新警告" - -#: src/view/com/composer/Composer.tsx:436 -msgid "Closes post composer and discards post draft" -msgstr "關閉貼文編輯頁並捨棄草稿" - -#: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:37 -msgid "Closes viewer for header image" -msgstr "關閉標題圖片檢視器" - -#: src/view/com/notifications/FeedItem.tsx:207 -msgid "Collapse list of users" -msgstr "折疊用戶清單" - -#: src/view/com/notifications/FeedItem.tsx:343 -msgid "Collapses list of users for a given notification" -msgstr "折疊指定通知的用戶清單" - -#: src/screens/Onboarding/index.tsx:38 -msgid "Comedy" -msgstr "喜劇" - -#: src/screens/Onboarding/index.tsx:24 -msgid "Comics" -msgstr "漫畫" - -#: src/Navigation.tsx:254 -#: src/view/screens/CommunityGuidelines.tsx:32 -msgid "Community Guidelines" -msgstr "社群守則" - -#: src/screens/Onboarding/StepFinished.tsx:181 -msgid "Complete onboarding and start using your account" -msgstr "完成初始設定並開始使用您的帳號" - -#: src/screens/Signup/index.tsx:168 -msgid "Complete the challenge" -msgstr "完成驗證" - -#: src/view/com/composer/Composer.tsx:553 -msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" -msgstr "撰寫貼文的長度最多為 {MAX_GRAPHEME_LENGTH} 個字元" - -#: src/view/com/composer/Prompt.tsx:26 -msgid "Compose reply" -msgstr "撰寫回覆" - -#: src/components/moderation/LabelPreference.tsx:81 -msgid "Configure content filtering setting for category: {name}" -msgstr "為 {name} 配置內容過濾設定" - -#: src/components/moderation/LabelPreference.tsx:244 -msgid "Configured in <0>moderation settings." -msgstr "已在<0>內容管理設定中配置。" - -#: src/components/Prompt.tsx:162 -#: src/components/Prompt.tsx:165 -#: src/view/com/modals/SelfLabel.tsx:155 -#: src/view/com/modals/VerifyEmail.tsx:239 -#: src/view/com/modals/VerifyEmail.tsx:241 -#: src/view/screens/PreferencesFollowingFeed.tsx:307 -#: src/view/screens/PreferencesThreads.tsx:159 -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:180 -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:183 -msgid "Confirm" -msgstr "確認" - -#: src/view/com/modals/ChangeEmail.tsx:188 -#: src/view/com/modals/ChangeEmail.tsx:190 -msgid "Confirm Change" -msgstr "確認更改" - -#: src/view/com/modals/lang-settings/ConfirmLanguagesButton.tsx:35 -msgid "Confirm content language settings" -msgstr "確認內容語言設定" - -#: src/view/com/modals/DeleteAccount.tsx:282 -msgid "Confirm delete account" -msgstr "確認刪除帳號" - -#: src/screens/Moderation/index.tsx:301 -msgid "Confirm your age:" -msgstr "確認您的年齡:" - -#: src/screens/Moderation/index.tsx:292 -msgid "Confirm your birthdate" -msgstr "確認您的出生日期" - -#: src/screens/Login/LoginForm.tsx:250 -#: src/view/com/modals/ChangeEmail.tsx:152 -#: src/view/com/modals/DeleteAccount.tsx:238 -#: src/view/com/modals/DeleteAccount.tsx:244 -#: src/view/com/modals/VerifyEmail.tsx:173 -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:143 -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:149 -msgid "Confirmation code" -msgstr "驗證碼" - -#: src/screens/Login/LoginForm.tsx:302 -msgid "Connecting..." -msgstr "連線中…" - -#: src/screens/Signup/index.tsx:238 -msgid "Contact support" -msgstr "聯繫支援" - -#: src/lib/moderation/useGlobalLabelStrings.ts:18 -msgid "Content Blocked" -msgstr "已封鎖內容" - -#: src/screens/Moderation/index.tsx:285 -msgid "Content filters" -msgstr "內容過濾" - -#: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:74 -#: src/view/screens/LanguageSettings.tsx:280 -msgid "Content Languages" -msgstr "內容語言" - -#: src/components/moderation/ModerationDetailsDialog.tsx:75 -#: src/lib/moderation/useModerationCauseDescription.ts:77 -msgid "Content Not Available" -msgstr "內容不可用" - -#: src/components/moderation/ModerationDetailsDialog.tsx:46 -#: src/components/moderation/ScreenHider.tsx:99 -#: src/lib/moderation/useGlobalLabelStrings.ts:22 -#: src/lib/moderation/useModerationCauseDescription.ts:40 -msgid "Content Warning" -msgstr "內容警告" - -#: src/view/com/composer/labels/LabelsBtn.tsx:32 -msgid "Content warnings" -msgstr "內容警告" - -#: src/components/Menu/index.web.tsx:83 -msgid "Context menu backdrop, click to close the menu." -msgstr "彈出式選單背景,點擊以關閉選單。" - -#: src/screens/Onboarding/StepInterests/index.tsx:253 -#: src/screens/Onboarding/StepProfile/index.tsx:269 -msgid "Continue" -msgstr "繼續" - -#: src/components/AccountList.tsx:113 -msgid "Continue as {0} (currently signed in)" -msgstr "以 {0} 繼續 (目前已登入)" - -#: src/view/com/post-thread/PostThreadLoadMore.tsx:52 -msgid "Continue thread..." -msgstr "繼續載入討論串…" - -#: src/screens/Onboarding/StepInterests/index.tsx:250 -#: src/screens/Onboarding/StepProfile/index.tsx:266 -#: src/screens/Signup/index.tsx:213 -msgid "Continue to next step" -msgstr "繼續下一步" - -#: src/screens/Messages/List/ChatListItem.tsx:154 -msgid "Conversation deleted" -msgstr "對話已刪除" - -#: src/screens/Onboarding/index.tsx:41 -msgid "Cooking" -msgstr "烹飪" - -#: src/view/com/modals/AddAppPasswords.tsx:220 -#: src/view/com/modals/InviteCodes.tsx:183 -msgid "Copied" -msgstr "已複製" - -#: src/view/screens/Settings/index.tsx:263 -msgid "Copied build version to clipboard" -msgstr "已複製建構版本號至剪貼簿" - -#: src/components/dms/MessageMenu.tsx:57 -#: src/view/com/modals/AddAppPasswords.tsx:80 -#: src/view/com/modals/ChangeHandle.tsx:320 -#: src/view/com/modals/InviteCodes.tsx:153 -#: src/view/com/util/forms/PostDropdownBtn.tsx:189 -msgid "Copied to clipboard" -msgstr "已複製至剪貼簿" - -#: src/components/dialogs/Embed.tsx:134 -msgid "Copied!" -msgstr "已複製!" - -#: src/view/com/modals/AddAppPasswords.tsx:214 -msgid "Copies app password" -msgstr "複製應用程式專用密碼" - -#: src/view/com/modals/AddAppPasswords.tsx:213 -msgid "Copy" -msgstr "複製" - -#: src/view/com/modals/ChangeHandle.tsx:474 -msgid "Copy {0}" -msgstr "複製{0}" - -#: src/components/dialogs/Embed.tsx:120 -#: src/components/dialogs/Embed.tsx:139 -msgid "Copy code" -msgstr "複製程式碼" - -#: src/view/screens/ProfileList.tsx:428 -msgid "Copy link to list" -msgstr "複製列表連結" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:307 -#: src/view/com/util/forms/PostDropdownBtn.tsx:316 -msgid "Copy link to post" -msgstr "複製貼文連結" - -#: src/components/dms/MessageMenu.tsx:110 -#: src/components/dms/MessageMenu.tsx:112 -msgid "Copy message text" -msgstr "複製訊息文字" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:285 -#: src/view/com/util/forms/PostDropdownBtn.tsx:287 -msgid "Copy post text" -msgstr "複製貼文文字" - -#: src/Navigation.tsx:259 -#: src/view/screens/CopyrightPolicy.tsx:29 -msgid "Copyright Policy" -msgstr "著作權政策" - -#: src/components/dms/LeaveConvoPrompt.tsx:39 -msgid "Could not leave chat" -msgstr "無法離開對話" - -#: src/view/screens/ProfileFeed.tsx:102 -msgid "Could not load feed" -msgstr "無法載入動態" - -#: src/view/screens/ProfileList.tsx:961 -msgid "Could not load list" -msgstr "無法載入列表" - -#: src/components/dms/ConvoMenu.tsx:88 -msgid "Could not mute chat" -msgstr "無法靜音對話" - -#: src/view/com/auth/SplashScreen.tsx:57 -#: src/view/com/auth/SplashScreen.web.tsx:106 -msgid "Create a new account" -msgstr "建立新帳號" - -#: src/view/screens/Settings/index.tsx:423 -msgid "Create a new Bluesky account" -msgstr "建立新的 Bluesky 帳號" - -#: src/screens/Signup/index.tsx:141 -msgid "Create Account" -msgstr "建立帳號" - -#: src/components/dialogs/Signin.tsx:86 -#: src/components/dialogs/Signin.tsx:88 -msgid "Create an account" -msgstr "建立一個帳號" - -#: src/screens/Onboarding/StepProfile/index.tsx:283 -msgid "Create an avatar instead" -msgstr "或是建立一個頭像" - -#: src/view/com/modals/AddAppPasswords.tsx:242 -msgid "Create App Password" -msgstr "建立應用程式專用密碼" - -#: src/view/com/auth/SplashScreen.tsx:48 -#: src/view/com/auth/SplashScreen.web.tsx:97 -msgid "Create new account" -msgstr "建立新帳號" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:98 -msgid "Create report for {0}" -msgstr "建立 {0} 的檢舉" - -#: src/view/screens/AppPasswords.tsx:251 -msgid "Created {0}" -msgstr "{0} 已建立" - -#: src/screens/Onboarding/index.tsx:26 -msgid "Culture" -msgstr "文化" - -#: src/view/com/auth/server-input/index.tsx:97 -#: src/view/com/auth/server-input/index.tsx:99 -msgid "Custom" -msgstr "自訂" - -#: src/view/com/modals/ChangeHandle.tsx:382 -msgid "Custom domain" -msgstr "自訂網域" - -#: src/view/screens/Feeds.tsx:763 -#: src/view/screens/Search/Explore.tsx:383 -msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." -msgstr "由社群打造的自訂動態源帶來全新體驗,幫助您找到所愛的內容。" - -#: src/view/screens/PreferencesExternalEmbeds.tsx:56 -msgid "Customize media from external sites." -msgstr "自訂外部網站的媒體。" - -#: src/view/screens/Settings/index.tsx:458 -#: src/view/screens/Settings/index.tsx:484 -msgid "Dark" -msgstr "深色" - -#: src/view/screens/Debug.tsx:63 -msgid "Dark mode" -msgstr "深色模式" - -#: src/view/screens/Settings/index.tsx:471 -msgid "Dark Theme" -msgstr "深色主題" - -#: src/screens/Signup/StepInfo/index.tsx:134 -msgid "Date of birth" -msgstr "出生日期" - -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:73 -#: src/view/screens/Settings/index.tsx:806 -msgid "Deactivate account" -msgstr "停用帳號" - -#: src/view/screens/Settings/index.tsx:818 -msgid "Deactivate my account" -msgstr "停用我的帳號" - -#: src/view/screens/Settings/index.tsx:873 -msgid "Debug Moderation" -msgstr "內容管理偵錯" - -#: src/view/screens/Debug.tsx:83 -msgid "Debug panel" -msgstr "偵錯面板" - -#: src/components/dms/MessageMenu.tsx:151 -#: src/view/com/util/forms/PostDropdownBtn.tsx:433 -#: src/view/screens/AppPasswords.tsx:285 -#: src/view/screens/ProfileList.tsx:667 -msgid "Delete" -msgstr "刪除" - -#: src/view/screens/Settings/index.tsx:828 -msgid "Delete account" -msgstr "刪除帳號" - -#: src/view/com/modals/DeleteAccount.tsx:105 -msgid "Delete Account <0>\"<1>{0}<2>\"" -msgstr "刪除帳號 <0>「<1>{0}<2>」" - -#: src/view/screens/AppPasswords.tsx:244 -msgid "Delete app password" -msgstr "刪除應用程式專用密碼" - -#: src/view/screens/AppPasswords.tsx:280 -msgid "Delete app password?" -msgstr "刪除應用程式專用密碼?" - -#: src/view/screens/Settings/index.tsx:890 -#: src/view/screens/Settings/index.tsx:893 -msgid "Delete chat declaration record" -msgstr "刪除對話聲明紀錄" - -#: src/components/dms/MessageMenu.tsx:124 -msgid "Delete for me" -msgstr "為我刪除" - -#: src/view/screens/ProfileList.tsx:471 -msgid "Delete List" -msgstr "刪除列表" - -#: src/components/dms/MessageMenu.tsx:147 -msgid "Delete message" -msgstr "刪除訊息" - -#: src/components/dms/MessageMenu.tsx:122 -msgid "Delete message for me" -msgstr "為我刪除訊息" - -#: src/view/com/modals/DeleteAccount.tsx:285 -msgid "Delete my account" -msgstr "刪除我的帳號" - -#: src/view/screens/Settings/index.tsx:840 -msgid "Delete My Account…" -msgstr "刪除我的帳號…" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:414 -#: src/view/com/util/forms/PostDropdownBtn.tsx:416 -msgid "Delete post" -msgstr "刪除貼文" - -#: src/view/screens/ProfileList.tsx:662 -msgid "Delete this list?" -msgstr "刪除此列表?" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:428 -msgid "Delete this post?" -msgstr "刪除這條貼文?" - -#: src/view/com/util/post-embeds/QuoteEmbed.tsx:84 -msgid "Deleted" -msgstr "已刪除" - -#: src/view/com/post-thread/PostThread.tsx:353 -msgid "Deleted post." -msgstr "已刪除的貼文。" - -#: src/view/screens/Settings/index.tsx:891 -msgid "Deletes the chat declaration record" -msgstr "刪除對話聲明紀錄" - -#: src/view/com/modals/CreateOrEditList.tsx:289 -#: src/view/com/modals/CreateOrEditList.tsx:310 -#: src/view/com/modals/EditProfile.tsx:199 -#: src/view/com/modals/EditProfile.tsx:211 -msgid "Description" -msgstr "描述" - -#: src/view/com/composer/GifAltText.tsx:140 -msgid "Descriptive alt text" -msgstr "生動的替代文字" - -#: src/view/com/composer/Composer.tsx:277 -msgid "Did you want to say anything?" -msgstr "有什麼想說的嗎?" - -#: src/view/screens/Settings/index.tsx:477 -msgid "Dim" -msgstr "昏暗" - -#: src/components/dms/MessagesNUX.tsx:88 -msgid "Direct messages are here!" -msgstr "私人訊息已推出!" - -#: src/view/screens/AccessibilitySettings.tsx:107 -msgid "Disable autoplay for GIFs" -msgstr "關閉 GIF 自動播放" - -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:90 -msgid "Disable Email 2FA" -msgstr "關閉電子郵件雙重驗證" - -#: src/view/screens/AccessibilitySettings.tsx:121 -msgid "Disable haptic feedback" -msgstr "關閉觸覺回饋" - -#: src/lib/moderation/useLabelBehaviorDescription.ts:32 -#: src/lib/moderation/useLabelBehaviorDescription.ts:42 -#: src/lib/moderation/useLabelBehaviorDescription.ts:68 -#: src/screens/Messages/Settings.tsx:140 -#: src/screens/Messages/Settings.tsx:143 -#: src/screens/Moderation/index.tsx:341 -msgid "Disabled" -msgstr "停用" - -#: src/view/com/composer/Composer.tsx:634 -msgid "Discard" -msgstr "捨棄" - -#: src/view/com/composer/Composer.tsx:631 -msgid "Discard draft?" -msgstr "捨棄草稿?" - -#: src/screens/Moderation/index.tsx:518 -#: src/screens/Moderation/index.tsx:522 -msgid "Discourage apps from showing my account to logged-out users" -msgstr "阻撓應用程式向未登入用戶顯示我的帳號" - -#: src/view/com/posts/FollowingEmptyState.tsx:70 -#: src/view/com/posts/FollowingEndOfFeed.tsx:71 -msgid "Discover new custom feeds" -msgstr "探索新的自訂動態源" - -#: src/view/screens/Search/Explore.tsx:381 -msgid "Discover new feeds" -msgstr "探索新的動態源" - -#: src/view/screens/Feeds.tsx:760 -msgid "Discover New Feeds" -msgstr "探索新的動態源" - -#: src/view/screens/AccessibilitySettings.tsx:95 -msgid "Display larger alt text badges" -msgstr "顯示更大的 alt 文本標識" - -#: src/view/com/modals/EditProfile.tsx:193 -msgid "Display name" -msgstr "顯示名稱" - -#: src/view/com/modals/EditProfile.tsx:181 -msgid "Display Name" -msgstr "顯示名稱" - -#: src/view/com/modals/ChangeHandle.tsx:391 -msgid "DNS Panel" -msgstr "DNS 控制台" - -#: src/lib/moderation/useGlobalLabelStrings.ts:39 -msgid "Does not include nudity." -msgstr "不包含裸露內容。" - -#: src/screens/Signup/StepHandle.tsx:105 -msgid "Doesn't begin or end with a hyphen" -msgstr "不以連字符開頭或結尾" - -#: src/view/com/modals/ChangeHandle.tsx:475 -msgid "Domain Value" -msgstr "網域設定值" - -#: src/view/com/modals/ChangeHandle.tsx:482 -msgid "Domain verified!" -msgstr "網域已驗證!" - -#: src/components/dialogs/BirthDateSettings.tsx:119 -#: src/components/dialogs/BirthDateSettings.tsx:125 -#: src/components/forms/DateField/index.tsx:77 -#: src/components/forms/DateField/index.tsx:83 -#: src/screens/Onboarding/StepProfile/index.tsx:322 -#: src/screens/Onboarding/StepProfile/index.tsx:325 -#: src/view/com/auth/server-input/index.tsx:169 -#: src/view/com/auth/server-input/index.tsx:170 -#: src/view/com/modals/AddAppPasswords.tsx:242 -#: src/view/com/modals/AltImage.tsx:141 -#: src/view/com/modals/crop-image/CropImage.web.tsx:177 -#: src/view/com/modals/InviteCodes.tsx:81 -#: src/view/com/modals/InviteCodes.tsx:124 -#: src/view/com/modals/ListAddRemoveUsers.tsx:142 -#: src/view/screens/PreferencesFollowingFeed.tsx:310 -msgid "Done" -msgstr "完成" - -#: src/view/com/modals/EditImage.tsx:334 -#: src/view/com/modals/ListAddRemoveUsers.tsx:144 -#: src/view/com/modals/SelfLabel.tsx:158 -#: src/view/com/modals/Threadgate.tsx:133 -#: src/view/com/modals/Threadgate.tsx:136 -#: src/view/com/modals/UserAddRemoveLists.tsx:108 -#: src/view/com/modals/UserAddRemoveLists.tsx:111 -#: src/view/screens/PreferencesThreads.tsx:162 -msgctxt "action" -msgid "Done" -msgstr "完成" - -#: src/view/com/modals/lang-settings/ConfirmLanguagesButton.tsx:43 -msgid "Done{extraText}" -msgstr "完成{extraText}" - -#: src/view/screens/Settings/ExportCarDialog.tsx:77 -#: src/view/screens/Settings/ExportCarDialog.tsx:81 -msgid "Download CAR file" -msgstr "下載 CAR 檔案" - -#: src/view/com/composer/text-input/TextInput.web.tsx:272 -msgid "Drop to add images" -msgstr "拖放即可新增圖片" - -#: src/view/com/modals/ChangeHandle.tsx:252 -msgid "e.g. alice" -msgstr "例如:alice" - -#: src/view/com/modals/EditProfile.tsx:186 -msgid "e.g. Alice Roberts" -msgstr "例如:張藍天" - -#: src/view/com/modals/ChangeHandle.tsx:374 -msgid "e.g. alice.com" -msgstr "例如:alice.com" - -#: src/view/com/modals/EditProfile.tsx:204 -msgid "e.g. Artist, dog-lover, and avid reader." -msgstr "例如:藝術家、愛狗人士和狂熱讀者。" - -#: src/lib/moderation/useGlobalLabelStrings.ts:43 -msgid "E.g. artistic nudes." -msgstr "例如:藝術裸露。" - -#: src/view/com/modals/CreateOrEditList.tsx:272 -msgid "e.g. Great Posters" -msgstr "例如:優秀的發文者" - -#: src/view/com/modals/CreateOrEditList.tsx:273 -msgid "e.g. Spammers" -msgstr "例如:垃圾內容製造者" - -#: src/view/com/modals/CreateOrEditList.tsx:301 -msgid "e.g. The posters who never miss." -msgstr "例如:絕對不容錯過的發文者。" - -#: src/view/com/modals/CreateOrEditList.tsx:302 -msgid "e.g. Users that repeatedly reply with ads." -msgstr "例如:多次張貼廣告的用戶。" - -#: src/view/com/modals/InviteCodes.tsx:97 -msgid "Each code works once. You'll receive more invite codes periodically." -msgstr "每個邀請碼僅能使用一次。您將定期收到更多的邀請碼。" - -#: src/view/com/lists/ListMembers.tsx:149 -msgctxt "action" -msgid "Edit" -msgstr "編輯" - -#: src/view/screens/Feeds.tsx:370 -#: src/view/screens/Feeds.tsx:441 -msgid "Edit" -msgstr "編輯" - -#: src/view/com/util/UserAvatar.tsx:325 -#: src/view/com/util/UserBanner.tsx:92 -msgid "Edit avatar" -msgstr "編輯頭像" - -#: src/view/com/composer/photos/Gallery.tsx:151 -#: src/view/com/modals/EditImage.tsx:208 -msgid "Edit image" -msgstr "編輯圖片" - -#: src/view/screens/ProfileList.tsx:459 -msgid "Edit list details" -msgstr "編輯列表詳情" - -#: src/view/com/modals/CreateOrEditList.tsx:239 -msgid "Edit Moderation List" -msgstr "編輯內容管理列表" - -#: src/Navigation.tsx:269 -#: src/view/screens/Feeds.tsx:368 -#: src/view/screens/Feeds.tsx:439 -#: src/view/screens/SavedFeeds.tsx:93 -msgid "Edit My Feeds" -msgstr "編輯我的動態源" - -#: src/view/com/modals/EditProfile.tsx:153 -msgid "Edit my profile" -msgstr "編輯我的個人檔案" - -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:181 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:179 -msgid "Edit profile" -msgstr "編輯個人檔案" - -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:184 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:182 -msgid "Edit Profile" -msgstr "編輯個人檔案" - -#: src/view/com/modals/CreateOrEditList.tsx:234 -msgid "Edit User List" -msgstr "編輯用戶列表" - -#: src/view/com/threadgate/WhoCanReply.tsx:73 -#: src/view/com/threadgate/WhoCanReply.tsx:130 -msgid "Edit who can reply" -msgstr "編輯「誰可以回覆」" - -#: src/view/com/modals/EditProfile.tsx:194 -msgid "Edit your display name" -msgstr "編輯您的顯示名稱" - -#: src/view/com/modals/EditProfile.tsx:212 -msgid "Edit your profile description" -msgstr "編輯您的帳號描述" - -#: src/screens/Onboarding/index.tsx:31 -msgid "Education" -msgstr "教育" - -#: src/screens/Signup/StepInfo/index.tsx:80 -#: src/view/com/modals/ChangeEmail.tsx:136 -msgid "Email" -msgstr "電子郵件" - -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:64 -msgid "Email 2FA disabled" -msgstr "已關閉電子郵件雙重驗證" - -#: src/screens/Login/ForgotPasswordForm.tsx:99 -msgid "Email address" -msgstr "電子郵件地址" - -#: src/view/com/modals/ChangeEmail.tsx:54 -#: src/view/com/modals/ChangeEmail.tsx:83 -msgid "Email updated" -msgstr "電子郵件已更新" - -#: src/view/com/modals/ChangeEmail.tsx:106 -msgid "Email Updated" -msgstr "電子郵件已更新" - -#: src/view/com/modals/VerifyEmail.tsx:85 -msgid "Email verified" -msgstr "電子郵件已驗證" - -#: src/view/screens/Settings/index.tsx:349 -msgid "Email:" -msgstr "電子郵件:" - -#: src/components/dialogs/Embed.tsx:112 -msgid "Embed HTML code" -msgstr "嵌入 HTML 程式碼" - -#: src/components/dialogs/Embed.tsx:97 -#: src/view/com/util/forms/PostDropdownBtn.tsx:324 -#: src/view/com/util/forms/PostDropdownBtn.tsx:326 -msgid "Embed post" -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 "將這則貼文嵌入到您的網站。只需複製以下程式碼片段,並將其貼上到您網站的 HTML 程式碼中即可。" - -#: src/components/dialogs/EmbedConsent.tsx:101 -msgid "Enable {0} only" -msgstr "僅啟用 {0}" - -#: src/screens/Moderation/index.tsx:329 -msgid "Enable adult content" -msgstr "顯示成人內容" - -#: src/components/dialogs/EmbedConsent.tsx:82 -#: src/components/dialogs/EmbedConsent.tsx:89 -msgid "Enable external media" -msgstr "啟用外部媒體" - -#: src/view/screens/PreferencesExternalEmbeds.tsx:76 -msgid "Enable media players for" -msgstr "啟用媒體播放器" - -#: src/view/screens/PreferencesFollowingFeed.tsx:146 -msgid "Enable this setting to only see replies between people you follow." -msgstr "啟用此設定將只顯示您跟隨的人之間的回覆。" - -#: src/components/dialogs/EmbedConsent.tsx:94 -msgid "Enable this source only" -msgstr "僅啟用此來源" - -#: src/screens/Messages/Settings.tsx:131 -#: src/screens/Messages/Settings.tsx:134 -#: src/screens/Moderation/index.tsx:339 -msgid "Enabled" -msgstr "啟用" - -#: src/screens/Profile/Sections/Feed.tsx:104 -msgid "End of feed" -msgstr "已經到底部啦!" - -#: src/view/com/modals/AddAppPasswords.tsx:160 -msgid "Enter a name for this App Password" -msgstr "輸入此應用程式專用密碼的名稱" - -#: src/screens/Login/SetNewPasswordForm.tsx:139 -msgid "Enter a password" -msgstr "輸入密碼" - -#: src/components/dialogs/MutedWords.tsx:99 -#: src/components/dialogs/MutedWords.tsx:100 -msgid "Enter a word or tag" -msgstr "輸入文字或標籤" - -#: src/view/com/modals/VerifyEmail.tsx:113 -msgid "Enter Confirmation Code" -msgstr "輸入驗證碼" - -#: src/view/com/modals/ChangePassword.tsx:154 -msgid "Enter the code you received to change your password." -msgstr "輸入您收到的驗證碼以更改密碼。" - -#: src/view/com/modals/ChangeHandle.tsx:364 -msgid "Enter the domain you want to use" -msgstr "輸入您想使用的網域" - -#: src/screens/Login/ForgotPasswordForm.tsx:119 -msgid "Enter the email you used to create your account. We'll send you a \"reset code\" so you can set a new password." -msgstr "輸入您用於建立帳號的電子郵件。我們將向您發送一個「重設碼」,來讓您設定新密碼。" - -#: src/components/dialogs/BirthDateSettings.tsx:108 -msgid "Enter your birth date" -msgstr "輸入您的出生日期" - -#: src/screens/Login/ForgotPasswordForm.tsx:105 -#: src/screens/Signup/StepInfo/index.tsx:92 -msgid "Enter your email address" -msgstr "輸入您的電子郵件地址" - -#: src/view/com/modals/ChangeEmail.tsx:42 -msgid "Enter your new email above" -msgstr "請在上方輸入您的新電子郵件地址" - -#: src/view/com/modals/ChangeEmail.tsx:112 -msgid "Enter your new email address below." -msgstr "請在下方輸入您的新電子郵件地址。" - -#: src/screens/Login/index.tsx:101 -msgid "Enter your username and password" -msgstr "輸入您的用戶名稱和密碼" - -#: src/view/screens/Settings/ExportCarDialog.tsx:46 -msgid "Error occurred while saving file" -msgstr "儲存檔案時發生錯誤" - -#: src/screens/Signup/StepCaptcha/index.tsx:51 -msgid "Error receiving captcha response." -msgstr "Captcha 給出了錯誤的回應。" - -#: src/screens/Onboarding/StepInterests/index.tsx:192 -#: src/view/screens/Search/Search.tsx:116 -msgid "Error:" -msgstr "錯誤:" - -#: src/view/com/modals/Threadgate.tsx:79 -msgid "Everybody" -msgstr "所有人" - -#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:46 -#: src/view/com/threadgate/WhoCanReply.tsx:64 -#: src/view/com/threadgate/WhoCanReply.tsx:121 -#: src/view/com/threadgate/WhoCanReply.tsx:235 -msgid "Everybody can reply" -msgstr "所有人都可以回覆" - -#: src/components/dms/MessagesNUX.tsx:131 -#: src/components/dms/MessagesNUX.tsx:134 -#: src/screens/Messages/Settings.tsx:75 -#: src/screens/Messages/Settings.tsx:78 -msgid "Everyone" -msgstr "所有人" - -#: src/lib/moderation/useReportOptions.ts:67 -msgid "Excessive mentions or replies" -msgstr "過多的提及或回覆" - -#: src/lib/moderation/useReportOptions.ts:80 -msgid "Excessive or unwanted messages" -msgstr "過多或不受歡迎的訊息" - -#: src/view/com/modals/DeleteAccount.tsx:293 -msgid "Exits account deletion process" -msgstr "離開刪除帳號流程" - -#: src/view/com/modals/ChangeHandle.tsx:145 -msgid "Exits handle change process" -msgstr "離開修改帳號代碼流程" - -#: src/view/com/modals/crop-image/CropImage.web.tsx:160 -msgid "Exits image cropping process" -msgstr "離開圖片裁剪流程" - -#: src/view/com/lightbox/Lightbox.web.tsx:130 -msgid "Exits image view" -msgstr "離開圖片檢視器" - -#: src/view/com/modals/ListAddRemoveUsers.tsx:88 -#: src/view/shell/desktop/Search.tsx:215 -msgid "Exits inputting search query" -msgstr "退出輸入搜索查詢" - -#: src/view/com/lightbox/Lightbox.web.tsx:183 -msgid "Expand alt text" -msgstr "展開替代文字" - -#: src/view/com/notifications/FeedItem.tsx:208 -msgid "Expand list of users" -msgstr "展開用戶清單" - -#: src/view/com/composer/ComposerReplyTo.tsx:82 -#: src/view/com/composer/ComposerReplyTo.tsx:85 -msgid "Expand or collapse the full post you are replying to" -msgstr "展開或摺疊您正在回覆的完整貼文" - -#: src/lib/moderation/useGlobalLabelStrings.ts:47 -msgid "Explicit or potentially disturbing media." -msgstr "露骨或可能令人不安的媒體內容。" - -#: src/lib/moderation/useGlobalLabelStrings.ts:35 -msgid "Explicit sexual images." -msgstr "露骨的色情圖片。" - -#: src/view/screens/Settings/index.tsx:786 -msgid "Export my data" -msgstr "匯出我的資料" - -#: src/view/screens/Settings/ExportCarDialog.tsx:62 -#: src/view/screens/Settings/index.tsx:797 -msgid "Export My Data" -msgstr "匯出我的資料" - -#: src/components/dialogs/EmbedConsent.tsx:55 -#: src/components/dialogs/EmbedConsent.tsx:59 -msgid "External Media" -msgstr "外部媒體" - -#: src/components/dialogs/EmbedConsent.tsx:71 -#: src/view/screens/PreferencesExternalEmbeds.tsx:67 -msgid "External media may allow websites to collect information about you and your device. No information is sent or requested until you press the \"play\" button." -msgstr "外部媒體可能允許網站收集有關您和您裝置的資料。在您按下「播放」按鈕之前,不會傳送或請求任何資料。" - -#: src/Navigation.tsx:288 -#: src/view/screens/PreferencesExternalEmbeds.tsx:53 -#: src/view/screens/Settings/index.tsx:679 -msgid "External Media Preferences" -msgstr "外部媒體偏好" - -#: src/view/screens/Settings/index.tsx:670 -msgid "External media settings" -msgstr "外部媒體設定" - -#: src/view/com/modals/AddAppPasswords.tsx:119 -#: src/view/com/modals/AddAppPasswords.tsx:123 -msgid "Failed to create app password." -msgstr "建立應用程式專用密碼失敗。" - -#: src/view/com/modals/CreateOrEditList.tsx:194 -msgid "Failed to create the list. Check your internet connection and try again." -msgstr "無法建立列表。請檢查您的網路連線並重試。" - -#: src/components/dms/MessageMenu.tsx:73 -msgid "Failed to delete message" -msgstr "無法刪除訊息" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:152 -msgid "Failed to delete post, please try again" -msgstr "無法刪除貼文,請重試" - -#: src/view/screens/Search/Explore.tsx:417 -#: src/view/screens/Search/Explore.tsx:441 -msgid "Failed to load feeds preferences" -msgstr "無法載入動態源偏好" - -#: src/components/dialogs/GifSelect.ios.tsx:196 -#: src/components/dialogs/GifSelect.tsx:212 -msgid "Failed to load GIFs" -msgstr "無法載入 GIF" - -#: src/screens/Messages/Conversation/MessageListError.tsx:23 -msgid "Failed to load past messages" -msgstr "無法載入過去的訊息" - -#: src/view/screens/Search/Explore.tsx:410 -#: src/view/screens/Search/Explore.tsx:434 -msgid "Failed to load suggested feeds" -msgstr "無法載入建議的動態源" - -#: src/view/screens/Search/Explore.tsx:370 -msgid "Failed to load suggested follows" -msgstr "無法載入建議的跟隨者" - -#: src/view/com/lightbox/Lightbox.tsx:84 -msgid "Failed to save image: {0}" -msgstr "無法儲存圖片:{0}" - -#: src/components/dms/MessageItem.tsx:230 -msgid "Failed to send" -msgstr "無法傳送" - -#: src/components/moderation/LabelsOnMeDialog.tsx:223 -#: src/screens/Messages/Conversation/ChatDisabled.tsx:87 -msgid "Failed to submit appeal, please try again." -msgstr "無法提交申訴,請重試。" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:180 -msgid "Failed to toggle thread mute, please try again" -msgstr "無法將討論串設為靜音,請重試" - -#: src/components/FeedCard.tsx:160 -msgid "Failed to update feeds" -msgstr "無法更新動態" - -#: src/components/dms/MessagesNUX.tsx:60 -#: src/screens/Messages/Settings.tsx:35 -msgid "Failed to update settings" -msgstr "無法更新設定" - -#: src/Navigation.tsx:209 -msgid "Feed" -msgstr "動態" - -#: src/components/FeedCard.tsx:91 -#: src/view/com/feeds/FeedSourceCard.tsx:251 -msgid "Feed by {0}" -msgstr "{0} 建立的動態源" - -#: src/view/screens/Feeds.tsx:675 -msgid "Feed offline" -msgstr "動態源已離線" - -#: src/view/shell/desktop/RightNav.tsx:66 -#: src/view/shell/Drawer.tsx:345 -msgid "Feedback" -msgstr "意見回饋" - -#: src/view/screens/Feeds.tsx:433 -#: src/view/screens/Feeds.tsx:536 -#: src/view/screens/Profile.tsx:197 -#: src/view/screens/Search/Search.tsx:375 -#: src/view/shell/desktop/LeftNav.tsx:367 -#: src/view/shell/Drawer.tsx:493 -#: src/view/shell/Drawer.tsx:494 -msgid "Feeds" -msgstr "動態源" - -#: src/view/screens/SavedFeeds.tsx:180 -msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." -msgstr "動態源是一種自訂演算法,使用者只需掌握一點開發技巧即可輕鬆構建。更多資訊請<0/>。" - -#: src/components/FeedCard.tsx:157 -msgid "Feeds updated!" -msgstr "動態已更新!" - -#: src/view/com/modals/ChangeHandle.tsx:475 -msgid "File Contents" -msgstr "檔案內容" - -#: src/view/screens/Settings/ExportCarDialog.tsx:42 -msgid "File saved successfully!" -msgstr "文件儲存成功!" - -#: src/lib/moderation/useLabelBehaviorDescription.ts:66 -msgid "Filter from feeds" -msgstr "動態源中的篩選" - -#: src/screens/Onboarding/StepFinished.tsx:184 -msgid "Finalizing" -msgstr "正在完成" - -#: src/view/com/posts/CustomFeedEmptyState.tsx:47 -#: src/view/com/posts/FollowingEmptyState.tsx:53 -#: src/view/com/posts/FollowingEndOfFeed.tsx:54 -msgid "Find accounts to follow" -msgstr "尋找一些帳號來跟隨" - -#: src/view/screens/Search/Search.tsx:439 -msgid "Find posts and users on Bluesky" -msgstr "在 Bluesky 上尋找貼文和用戶" - -#: src/view/screens/PreferencesFollowingFeed.tsx:110 -msgid "Fine-tune the content you see on your Following feed." -msgstr "對「Following」動態源中的內容進行微調,以下選項只對「Following」動態源起作用。" - -#: src/view/screens/PreferencesThreads.tsx:60 -msgid "Fine-tune the discussion threads." -msgstr "微調討論串。" - -#: src/screens/Onboarding/index.tsx:35 -msgid "Fitness" -msgstr "健康" - -#: src/screens/Onboarding/StepFinished.tsx:164 -msgid "Flexible" -msgstr "靈活" - -#: src/view/com/modals/EditImage.tsx:116 -msgid "Flip horizontal" -msgstr "水平翻轉" - -#: src/view/com/modals/EditImage.tsx:121 -#: src/view/com/modals/EditImage.tsx:288 -msgid "Flip vertically" -msgstr "垂直翻轉" - -#: src/components/ProfileHoverCard/index.web.tsx:446 -#: src/components/ProfileHoverCard/index.web.tsx:457 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:252 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:146 -#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:247 -msgid "Follow" -msgstr "跟隨" - -#: src/view/com/profile/FollowButton.tsx:69 -msgctxt "action" -msgid "Follow" -msgstr "跟隨" - -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:238 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:128 -msgid "Follow {0}" -msgstr "跟隨 {0}" - -#: src/view/com/posts/AviFollowButton.tsx:71 -msgid "Follow {name}" -msgstr "跟隨 {name}" - -#: src/view/com/profile/ProfileMenu.tsx:247 -#: src/view/com/profile/ProfileMenu.tsx:258 -msgid "Follow Account" -msgstr "跟隨帳號" - -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:144 -msgid "Follow Back" -msgstr "回追蹤" - -#: src/view/screens/Search/Explore.tsx:333 -msgid "Follow more accounts to get connected to your interests and build your network." -msgstr "跟隨更多帳號以瞭解您的興趣,並建立您的社群網路。" - -#: src/view/com/profile/ProfileCard.tsx:227 -msgid "Followed by {0}" -msgstr "由 {0} 跟隨" - -#: src/components/KnownFollowers.tsx:223 -msgid "Followed by <0>{0}" -msgstr "已被你跟隨的 <0>{0} 跟隨" - -#: src/components/KnownFollowers.tsx:209 -msgid "Followed by <0>{0} and {1, plural, one {# other} other {# others}}" -msgstr "已被你跟隨的 <0>{0} 和{1, plural, one {其他 # 人跟隨} other {其他 # 人跟}}" - -#: src/components/KnownFollowers.tsx:196 -msgid "Followed by <0>{0} and <1>{1}" -msgstr "已被你跟隨的 <0>{0} 和 <1>{1} 跟隨" - -#: src/components/KnownFollowers.tsx:178 -msgid "Followed by <0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}}" -msgstr "已被你跟隨的 <0>{0}, <1>{1} 和{2, plural, one {其他 # 人跟隨} other {其他 # 人跟隨}}" - -#: src/view/com/modals/Threadgate.tsx:101 -msgid "Followed users" -msgstr "已跟隨的用戶" - -#: src/view/screens/PreferencesFollowingFeed.tsx:153 -msgid "Followed users only" -msgstr "僅限已跟隨的用戶" - -#: src/view/com/notifications/FeedItem.tsx:175 -msgid "followed you" -msgstr "已跟隨您" - -#: src/view/com/profile/ProfileFollowers.tsx:104 -#: src/view/screens/ProfileFollowers.tsx:25 -msgid "Followers" -msgstr "跟隨者" - -#: src/Navigation.tsx:177 -msgid "Followers of @{0} that you know" -msgstr "您所認識的這些人也跟隨了 @{0}" - -#: src/screens/Profile/KnownFollowers.tsx:108 -#: src/screens/Profile/KnownFollowers.tsx:118 -msgid "Followers you know" -msgstr "您也認識的跟隨者" - -#: src/components/ProfileHoverCard/index.web.tsx:445 -#: src/components/ProfileHoverCard/index.web.tsx:456 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:250 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:149 -#: src/view/com/profile/ProfileFollows.tsx:104 -#: src/view/screens/Feeds.tsx:622 -#: src/view/screens/ProfileFollows.tsx:25 -#: src/view/screens/SavedFeeds.tsx:415 -msgid "Following" -msgstr "跟隨中" - -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:98 -msgid "Following {0}" -msgstr "已跟隨 {0}" - -#: src/view/com/posts/AviFollowButton.tsx:53 -msgid "Following {name}" -msgstr "已跟隨 {name}" - -#: src/view/screens/Settings/index.tsx:573 -msgid "Following feed preferences" -msgstr "「Following」動態源偏好" - -#: src/Navigation.tsx:275 -#: src/view/screens/PreferencesFollowingFeed.tsx:103 -#: src/view/screens/Settings/index.tsx:582 -msgid "Following Feed Preferences" -msgstr "「Following」動態源偏好" - -#: src/screens/Profile/Header/Handle.tsx:31 -msgid "Follows you" -msgstr "跟隨您" - -#: src/view/com/profile/ProfileCard.tsx:152 -msgid "Follows You" -msgstr "跟隨您" - -#: src/screens/Onboarding/index.tsx:40 -msgid "Food" -msgstr "食物" - -#: src/view/com/modals/DeleteAccount.tsx:129 -msgid "For security reasons, we'll need to send a confirmation code to your email address." -msgstr "為了保護您的帳號安全,我們需要將驗證碼發送到您的電子郵件地址。" - -#: src/view/com/modals/AddAppPasswords.tsx:232 -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 "為了保護您的帳號安全,您將無法再次查看此內容。如果您丟失了此密碼,您將需要再產生一個新的密碼。" - -#: src/screens/Login/index.tsx:129 -#: src/screens/Login/index.tsx:144 -msgid "Forgot Password" -msgstr "忘記密碼" - -#: src/screens/Login/LoginForm.tsx:224 -msgid "Forgot password?" -msgstr "忘記密碼?" - -#: src/screens/Login/LoginForm.tsx:235 -msgid "Forgot?" -msgstr "忘記?" - -#: src/lib/moderation/useReportOptions.ts:53 -msgid "Frequently Posts Unwanted Content" -msgstr "頻繁發佈不當內容" - -#: src/screens/Hashtag.tsx:118 -msgid "From @{sanitizedAuthor}" -msgstr "來自 @{sanitizedAuthor}" - -#: src/view/com/posts/FeedItem.tsx:236 -msgctxt "from-feed" -msgid "From <0/>" -msgstr "來自 <0/>" - -#: src/view/com/composer/photos/SelectPhotoBtn.tsx:39 -msgid "Gallery" -msgstr "相簿" - -#: src/components/dms/MessagesNUX.tsx:168 -msgid "Get started" -msgstr "開始" - -#: src/view/com/modals/VerifyEmail.tsx:197 -#: src/view/com/modals/VerifyEmail.tsx:199 -msgid "Get Started" -msgstr "開始" - -#: src/view/com/util/images/ImageHorzList.tsx:35 -msgid "GIF" -msgstr "GIF" - -#: src/screens/Onboarding/StepProfile/index.tsx:225 -msgid "Give your profile a face" -msgstr "為您的個人檔案增添新顏" - -#: src/lib/moderation/useReportOptions.ts:38 -msgid "Glaring violations of law or terms of service" -msgstr "明顯違反法律或服務條款" - -#: src/components/moderation/ScreenHider.tsx:151 -#: src/components/moderation/ScreenHider.tsx:160 -#: src/view/com/auth/LoggedOut.tsx:82 -#: src/view/com/auth/LoggedOut.tsx:83 -#: src/view/screens/NotFound.tsx:55 -#: src/view/screens/ProfileFeed.tsx:111 -#: src/view/screens/ProfileList.tsx:970 -#: src/view/shell/desktop/LeftNav.tsx:127 -msgid "Go back" -msgstr "返回" - -#: src/components/Error.tsx:103 -#: src/screens/Profile/ErrorState.tsx:62 -#: src/screens/Profile/ErrorState.tsx:66 -#: src/view/screens/NotFound.tsx:54 -#: src/view/screens/ProfileFeed.tsx:116 -#: src/view/screens/ProfileList.tsx:975 -msgid "Go Back" -msgstr "返回" - -#: src/components/dms/ReportDialog.tsx:154 -#: src/components/ReportDialog/SelectReportOptionView.tsx:77 -#: src/components/ReportDialog/SubmitView.tsx:104 -#: src/screens/Onboarding/Layout.tsx:102 -#: src/screens/Onboarding/Layout.tsx:191 -#: src/screens/Signup/index.tsx:187 -msgid "Go back to previous step" -msgstr "返回上一步" - -#: src/view/screens/NotFound.tsx:55 -msgid "Go home" -msgstr "前往首頁" - -#: src/view/screens/NotFound.tsx:54 -msgid "Go Home" -msgstr "前往首頁" - -#: src/screens/Messages/List/ChatListItem.tsx:211 -msgid "Go to conversation with {0}" -msgstr "與 {0} 對話" - -#: src/screens/Login/ForgotPasswordForm.tsx:172 -#: src/view/com/modals/ChangePassword.tsx:168 -msgid "Go to next" -msgstr "前往下一步" - -#: src/components/dms/ConvoMenu.tsx:167 -msgid "Go to profile" -msgstr "前往個人檔案" - -#: src/components/dms/ConvoMenu.tsx:164 -msgid "Go to user's profile" -msgstr "前往用戶的個人檔案" - -#: src/lib/moderation/useGlobalLabelStrings.ts:46 -msgid "Graphic Media" -msgstr "不適宜的圖像媒體" - -#: src/view/com/modals/ChangeHandle.tsx:260 -msgid "Handle" -msgstr "帳號代碼" - -#: src/view/screens/AccessibilitySettings.tsx:116 -msgid "Haptics" -msgstr "觸覺" - -#: src/lib/moderation/useReportOptions.ts:33 -msgid "Harassment, trolling, or intolerance" -msgstr "騷擾、惡作劇或其他無法容忍的行為" - -#: src/Navigation.tsx:303 -msgid "Hashtag" -msgstr "標籤" - -#: src/components/RichText.tsx:216 -msgid "Hashtag: #{tag}" -msgstr "標籤:#{tag}" - -#: src/screens/Signup/index.tsx:234 -msgid "Having trouble?" -msgstr "遇到問題?" - -#: src/view/shell/desktop/RightNav.tsx:95 -#: src/view/shell/Drawer.tsx:355 -msgid "Help" -msgstr "幫助" - -#: src/screens/Onboarding/StepProfile/index.tsx:228 -msgid "Help people know you're not a bot by uploading a picture or creating an avatar." -msgstr "透過上傳圖片或建立頭像來幫助人們知道您不是機器人。" - -#: src/view/com/modals/AddAppPasswords.tsx:203 -msgid "Here is your app password." -msgstr "這是您的應用程式專用密碼。" - -#: src/components/moderation/ContentHider.tsx:116 -#: src/components/moderation/LabelPreference.tsx:134 -#: src/components/moderation/PostHider.tsx:122 -#: src/lib/moderation/useLabelBehaviorDescription.ts:15 -#: src/lib/moderation/useLabelBehaviorDescription.ts:20 -#: src/lib/moderation/useLabelBehaviorDescription.ts:25 -#: src/lib/moderation/useLabelBehaviorDescription.ts:30 -#: src/view/com/util/forms/PostDropdownBtn.tsx:442 -msgid "Hide" -msgstr "隱藏" - -#: src/view/com/notifications/FeedItem.tsx:350 -msgctxt "action" -msgid "Hide" -msgstr "隱藏" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:387 -#: src/view/com/util/forms/PostDropdownBtn.tsx:389 -msgid "Hide post" -msgstr "隱藏貼文" - -#: src/components/moderation/ContentHider.tsx:68 -#: src/components/moderation/PostHider.tsx:79 -msgid "Hide the content" -msgstr "隱藏內容" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:439 -msgid "Hide this post?" -msgstr "隱藏這則貼文?" - -#: src/view/com/notifications/FeedItem.tsx:341 -msgid "Hide user list" -msgstr "隱藏用戶列表" - -#: src/view/com/posts/FeedErrorMessage.tsx:117 -msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." -msgstr "抱歉,與動態源的伺服器連線時發生了某種問題。請向該動態源的擁有者報告這個問題。" - -#: src/view/com/posts/FeedErrorMessage.tsx:105 -msgid "Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue." -msgstr "抱歉,動態源的伺服器似乎設定錯誤。請向該動態源的擁有者報告這個問題。" - -#: src/view/com/posts/FeedErrorMessage.tsx:111 -msgid "Hmm, the feed server appears to be offline. Please let the feed owner know about this issue." -msgstr "抱歉,動態源的伺服器似乎已離線。請向該動態源的擁有者報告這個問題。" - -#: src/view/com/posts/FeedErrorMessage.tsx:108 -msgid "Hmm, the feed server gave a bad response. Please let the feed owner know about this issue." -msgstr "抱歉,動態源的伺服器給出了錯誤的回應。請向該動態源的擁有者報告這個問題。" - -#: src/view/com/posts/FeedErrorMessage.tsx:102 -msgid "Hmm, we're having trouble finding this feed. It may have been deleted." -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 "抱歉,看起來我們在載入這些資料時遇到了問題,請參閱下方詳情。如果問題持續存在,請聯繫我們。" - -#: src/screens/Profile/ErrorState.tsx:31 -msgid "Hmmmm, we couldn't load that moderation service." -msgstr "抱歉,我們無法載入該內容管理服務。" - -#: src/Navigation.tsx:489 -#: src/view/shell/bottom-bar/BottomBar.tsx:159 -#: src/view/shell/desktop/LeftNav.tsx:335 -#: src/view/shell/Drawer.tsx:425 -#: src/view/shell/Drawer.tsx:426 -msgid "Home" -msgstr "首頁" - -#: src/view/com/modals/ChangeHandle.tsx:414 -msgid "Host:" -msgstr "主機:" - -#: src/screens/Login/ForgotPasswordForm.tsx:89 -#: src/screens/Login/LoginForm.tsx:157 -#: src/screens/Signup/StepInfo/index.tsx:40 -#: src/view/com/modals/ChangeHandle.tsx:275 -msgid "Hosting provider" -msgstr "託管服務供應商" - -#: src/view/com/modals/InAppBrowserConsent.tsx:44 -msgid "How should we open this link?" -msgstr "我們該如何開啟此連結?" - -#: src/view/com/modals/VerifyEmail.tsx:222 -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:132 -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:135 -msgid "I have a code" -msgstr "我有驗證碼" - -#: src/view/com/modals/VerifyEmail.tsx:224 -msgid "I have a confirmation code" -msgstr "我有驗證碼" - -#: src/view/com/modals/ChangeHandle.tsx:278 -msgid "I have my own domain" -msgstr "我擁有自己的網域" - -#: src/components/dms/BlockedByListDialog.tsx:56 -#: src/components/dms/ReportConversationPrompt.tsx:22 -msgid "I understand" -msgstr "我瞭解" - -#: src/view/com/lightbox/Lightbox.web.tsx:185 -msgid "If alt text is long, toggles alt text expanded state" -msgstr "替代文字過長時,切換替代文字的展開狀態" - -#: src/view/com/modals/SelfLabel.tsx:128 -msgid "If none are selected, suitable for all ages." -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 "如果根據您所在國家的法律,您尚未成年,則您的父母或法定監護人必須代表您閱讀這些條款。" - -#: src/view/screens/ProfileList.tsx:664 -msgid "If you delete this list, you won't be able to recover it." -msgstr "如果刪除這個列表,您將無法恢復它。" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:430 -msgid "If you remove this post, you won't be able to recover it." -msgstr "如果刪除這則貼文,您將無法恢復它。" - -#: src/view/com/modals/ChangePassword.tsx:149 -msgid "If you want to change your password, we will send you a code to verify that this is your account." -msgstr "如果您想更改密碼,我們將向您發送一個驗證碼以確認這是您的帳號。" - -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:92 -msgid "If you're trying to change your handle or email, do so before you deactivate." -msgstr "如果您想更改帳號代碼或電子郵件,請在停用帳號前更改。" - -#: src/lib/moderation/useReportOptions.ts:37 -msgid "Illegal and Urgent" -msgstr "違法" - -#: src/view/com/util/images/Gallery.tsx:42 -msgid "Image" -msgstr "圖片" - -#: src/view/com/modals/AltImage.tsx:122 -msgid "Image alt text" -msgstr "圖片替代文字" - -#: src/lib/moderation/useReportOptions.ts:48 -msgid "Impersonation or false claims about identity or affiliation" -msgstr "冒充或虛假聲明身份或隸屬關係" - -#: src/lib/moderation/useReportOptions.ts:85 -msgid "Inappropriate messages or explicit links" -msgstr "不當訊息或露骨連結" - -#: src/screens/Login/SetNewPasswordForm.tsx:127 -msgid "Input code sent to your email for password reset" -msgstr "輸入發送到您電子郵件地址的重設碼以重設密碼" - -#: src/view/com/modals/DeleteAccount.tsx:246 -msgid "Input confirmation code for account deletion" -msgstr "輸入刪除帳號的驗證碼" - -#: src/view/com/modals/AddAppPasswords.tsx:174 -msgid "Input name for app password" -msgstr "輸入應用程式專用密碼名稱" - -#: src/screens/Login/SetNewPasswordForm.tsx:151 -msgid "Input new password" -msgstr "輸入新密碼" - -#: src/view/com/modals/DeleteAccount.tsx:265 -msgid "Input password for account deletion" -msgstr "輸入密碼以刪除帳號" - -#: src/screens/Login/LoginForm.tsx:263 -msgid "Input the code which has been emailed to you" -msgstr "輸入寄送至您電子郵件地址的驗證碼" - -#: src/screens/Login/LoginForm.tsx:218 -msgid "Input the password tied to {identifier}" -msgstr "輸入與 {identifier} 關聯的密碼" - -#: src/screens/Login/LoginForm.tsx:191 -msgid "Input the username or email address you used at signup" -msgstr "輸入註冊時使用的用戶名稱或電子郵件地址" - -#: src/screens/Login/LoginForm.tsx:217 -msgid "Input your password" -msgstr "輸入您的密碼" - -#: src/view/com/modals/ChangeHandle.tsx:383 -msgid "Input your preferred hosting provider" -msgstr "輸入您的託管服務供應商" - -#: src/screens/Signup/StepHandle.tsx:63 -msgid "Input your user handle" -msgstr "輸入您的帳號代碼" - -#: src/components/dms/MessagesNUX.tsx:82 -msgid "Introducing Direct Messages" -msgstr "為您隆重介紹「私人訊息」" - -#: src/screens/Login/LoginForm.tsx:132 -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:70 -msgid "Invalid 2FA confirmation code." -msgstr "無效的雙重驗證碼。" - -#: src/view/com/post-thread/PostThreadItem.tsx:236 -msgid "Invalid or unsupported post record" -msgstr "無效或不支援的貼文紀錄" - -#: src/screens/Login/LoginForm.tsx:137 -msgid "Invalid username or password" -msgstr "用戶名稱或密碼無效" - -#: src/view/com/modals/InviteCodes.tsx:94 -msgid "Invite a Friend" -msgstr "邀請朋友" - -#: src/screens/Signup/StepInfo/index.tsx:58 -msgid "Invite code" -msgstr "邀請碼" - -#: src/screens/Signup/state.ts:275 -msgid "Invite code not accepted. Check that you input it correctly and try again." -msgstr "邀請碼無效。請檢查您輸入的內容是否正確,然後重試。" - -#: src/view/com/modals/InviteCodes.tsx:171 -msgid "Invite codes: {0} available" -msgstr "邀請碼:{0} 個可用" - -#: src/view/com/modals/InviteCodes.tsx:170 -msgid "Invite codes: 1 available" -msgstr "邀請碼:1 個可用" - -#: src/view/com/auth/SplashScreen.web.tsx:157 -msgid "Jobs" -msgstr "工作" - -#: src/screens/Onboarding/index.tsx:21 -msgid "Journalism" -msgstr "新聞學" - -#: src/components/moderation/ContentHider.tsx:147 -msgid "Labeled by {0}." -msgstr "由 {0} 標記。" - -#: src/components/moderation/ContentHider.tsx:145 -msgid "Labeled by the author." -msgstr "由作者標記。" - -#: src/view/screens/Profile.tsx:191 -msgid "Labels" -msgstr "標記" - -#: src/screens/Profile/Sections/Labels.tsx:163 -msgid "Labels are annotations on users and content. They can be used to hide, warn, and categorize the network." -msgstr "標記是對用戶和內容的標註,可用於隱藏、警告和對網路進行分類。" - -#: src/components/moderation/LabelsOnMeDialog.tsx:79 -msgid "Labels on your account" -msgstr "您帳號上的標記" - -#: src/components/moderation/LabelsOnMeDialog.tsx:81 -msgid "Labels on your content" -msgstr "您內容上的標記" - -#: src/view/com/composer/select-language/SelectLangBtn.tsx:105 -msgid "Language selection" -msgstr "語言選擇" - -#: src/view/screens/Settings/index.tsx:530 -msgid "Language settings" -msgstr "語言設定" - -#: src/Navigation.tsx:150 -#: src/view/screens/LanguageSettings.tsx:90 -msgid "Language Settings" -msgstr "語言設定" - -#: src/view/screens/Settings/index.tsx:539 -msgid "Languages" -msgstr "語言" - -#: src/screens/Hashtag.tsx:99 -#: src/view/screens/Search/Search.tsx:359 -msgid "Latest" -msgstr "最新" - -#: src/components/moderation/ScreenHider.tsx:136 -msgid "Learn More" -msgstr "瞭解詳情" - -#: src/components/moderation/ContentHider.tsx:66 -#: src/components/moderation/ContentHider.tsx:131 -msgid "Learn more about the moderation applied to this content." -msgstr "詳細瞭解套用於此內容的內容管理。" - -#: src/components/moderation/PostHider.tsx:100 -#: src/components/moderation/ScreenHider.tsx:125 -msgid "Learn more about this warning" -msgstr "瞭解有關此警告的更多資訊" - -#: src/screens/Moderation/index.tsx:549 -msgid "Learn more about what is public on Bluesky." -msgstr "瞭解有關 Bluesky 上公開內容的更多資訊。" - -#: src/components/moderation/ContentHider.tsx:155 -msgid "Learn more." -msgstr "瞭解詳情。" - -#: src/components/dms/LeaveConvoPrompt.tsx:50 -msgid "Leave" -msgstr "離開" - -#: src/components/dms/MessagesListBlockedFooter.tsx:66 -#: src/components/dms/MessagesListBlockedFooter.tsx:73 -msgid "Leave chat" -msgstr "離開對話" - -#: src/components/dms/ConvoMenu.tsx:138 -#: src/components/dms/ConvoMenu.tsx:141 -#: src/components/dms/ConvoMenu.tsx:208 -#: src/components/dms/ConvoMenu.tsx:211 -#: src/components/dms/LeaveConvoPrompt.tsx:46 -msgid "Leave conversation" -msgstr "離開對話" - -#: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:82 -msgid "Leave them all unchecked to see any language." -msgstr "全部留空以查看所有語言。" - -#: src/view/com/modals/LinkWarning.tsx:65 -msgid "Leaving Bluesky" -msgstr "離開 Bluesky" - -#: src/screens/SignupQueued.tsx:134 -msgid "left to go." -msgstr "個人在排在您前面。" - -#: src/view/screens/Settings/index.tsx:308 -msgid "Legacy storage cleared, you need to restart the app now." -msgstr "遺留資料已清除,您需要立即重新啟動應用程式。" - -#: src/screens/Login/index.tsx:130 -#: src/screens/Login/index.tsx:145 -msgid "Let's get your password reset!" -msgstr "讓我們來重設您的密碼吧!" - -#: src/screens/Onboarding/StepFinished.tsx:184 -msgid "Let's go!" -msgstr "讓我們開始吧!" - -#: src/view/screens/Settings/index.tsx:452 -msgid "Light" -msgstr "亮色" - -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:267 -#: src/view/screens/ProfileFeed.tsx:570 -msgid "Like this feed" -msgstr "對這個動態源按喜歡" - -#: src/components/LikesDialog.tsx:87 -#: src/Navigation.tsx:214 -#: src/Navigation.tsx:219 -msgid "Liked by" -msgstr "按喜歡的用戶" - -#: src/screens/Profile/ProfileLabelerLikedBy.tsx:29 -#: src/view/screens/PostLikedBy.tsx:27 -#: src/view/screens/ProfileFeedLikedBy.tsx:27 -msgid "Liked By" -msgstr "按喜歡的用戶" - -#: src/view/com/notifications/FeedItem.tsx:178 -msgid "liked your custom feed" -msgstr "對您的自訂動態源表示喜歡" - -#: src/view/com/notifications/FeedItem.tsx:170 -msgid "liked your post" -msgstr "已喜歡您的貼文" - -#: src/view/screens/Profile.tsx:196 -msgid "Likes" -msgstr "喜歡" - -#: src/view/com/post-thread/PostThreadItem.tsx:197 -msgid "Likes on this post" -msgstr "這條貼文的喜歡數" - -#: src/Navigation.tsx:183 -msgid "List" -msgstr "列表" - -#: src/view/com/modals/CreateOrEditList.tsx:250 -msgid "List Avatar" -msgstr "列表頭像" - -#: src/view/screens/ProfileList.tsx:358 -msgid "List blocked" -msgstr "列表已封鎖" - -#: src/view/com/feeds/FeedSourceCard.tsx:253 -msgid "List by {0}" -msgstr "列表由 {0} 建立" - -#: src/view/screens/ProfileList.tsx:397 -msgid "List deleted" -msgstr "列表已刪除" - -#: src/view/screens/ProfileList.tsx:330 -msgid "List muted" -msgstr "列表已靜音" - -#: src/view/com/modals/CreateOrEditList.tsx:264 -msgid "List Name" -msgstr "列表名稱" - -#: src/view/screens/ProfileList.tsx:372 -msgid "List unblocked" -msgstr "已解除封鎖的列表" - -#: src/view/screens/ProfileList.tsx:344 -msgid "List unmuted" -msgstr "已解除靜音的列表" - -#: src/Navigation.tsx:120 -#: src/view/screens/Profile.tsx:192 -#: src/view/screens/Profile.tsx:198 -#: src/view/shell/desktop/LeftNav.tsx:373 -#: src/view/shell/Drawer.tsx:509 -#: src/view/shell/Drawer.tsx:510 -msgid "Lists" -msgstr "列表" - -#: src/components/dms/BlockedByListDialog.tsx:39 -msgid "Lists blocking this user:" -msgstr "封鎖此用戶的列表:" - -#: src/view/screens/Search/Explore.tsx:130 -msgid "Load more" -msgstr "載入更多" - -#: src/view/screens/Search/Explore.tsx:218 -msgid "Load more suggested feeds" -msgstr "載入更多推薦動態" - -#: src/view/screens/Search/Explore.tsx:216 -msgid "Load more suggested follows" -msgstr "載入更多推薦跟隨者" - -#: src/view/screens/Notifications.tsx:184 -msgid "Load new notifications" -msgstr "載入新的通知" - -#: src/screens/Profile/Sections/Feed.tsx:86 -#: src/view/com/feeds/FeedPage.tsx:136 -#: src/view/screens/ProfileFeed.tsx:492 -#: src/view/screens/ProfileList.tsx:749 -msgid "Load new posts" -msgstr "載入新的貼文" - -#: src/view/com/composer/text-input/mobile/Autocomplete.tsx:99 -msgid "Loading..." -msgstr "載入中…" - -#: src/Navigation.tsx:234 -msgid "Log" -msgstr "日誌" - -#: src/screens/Deactivated.tsx:214 -#: src/screens/Deactivated.tsx:220 -msgid "Log in or sign up" -msgstr "登入或註冊" - -#: src/screens/SignupQueued.tsx:155 -#: src/screens/SignupQueued.tsx:158 -#: src/screens/SignupQueued.tsx:184 -#: src/screens/SignupQueued.tsx:187 -msgid "Log out" -msgstr "登出" - -#: src/screens/Moderation/index.tsx:442 -msgid "Logged-out visibility" -msgstr "登出可見性" - -#: src/components/AccountList.tsx:58 -msgid "Login to account that is not listed" -msgstr "登入未列出的帳號" - -#: src/components/RichText.tsx:217 -msgid "Long press to open tag menu for #{tag}" -msgstr "長按開啟 #{tag} 的標籤選單" - -#: src/screens/Login/SetNewPasswordForm.tsx:116 -msgid "Looks like XXXXX-XXXXX" -msgstr "看起來像是 XXXXX-XXXXX" - -#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:39 -msgid "Looks like you haven't saved any feeds! Use our recommendations or browse more below." -msgstr "您似乎尚未儲存任何動態源!參考我們的建議或瀏覽下面的更多內容。" - -#: src/screens/Home/NoFeedsPinned.tsx:83 -msgid "Looks like you unpinned all your feeds. But don't worry, you can add some below 😄" -msgstr "看起來您已取消釘選所有動態源。但不用擔心,您可以在下面新增一些😄" - -#: src/screens/Feeds/NoFollowingFeed.tsx:37 -msgid "Looks like you're missing a following feed. <0>Click here to add one." -msgstr "您看起來需要「Following」動態源,<0>點選這裡來新增。" - -#: src/view/com/modals/LinkWarning.tsx:79 -msgid "Make sure this is where you intend to go!" -msgstr "請確認這是您想要去的的地方!" - -#: src/components/dialogs/MutedWords.tsx:82 -msgid "Manage your muted words and tags" -msgstr "管理您靜音的文字和標籤" - -#: src/components/dms/ConvoMenu.tsx:151 -#: src/components/dms/ConvoMenu.tsx:158 -msgid "Mark as read" -msgstr "標記為已讀" - -#: src/view/screens/AccessibilitySettings.tsx:102 -#: src/view/screens/Profile.tsx:195 -msgid "Media" -msgstr "媒體" - -#: src/view/com/threadgate/WhoCanReply.tsx:270 -msgid "mentioned users" -msgstr "被提及的用戶" - -#: src/view/com/modals/Threadgate.tsx:96 -msgid "Mentioned users" -msgstr "被提及的用戶" - -#: src/view/com/util/ViewHeader.tsx:91 -#: src/view/screens/Search/Search.tsx:683 -msgid "Menu" -msgstr "選單" - -#: src/components/dms/MessageProfileButton.tsx:67 -msgid "Message {0}" -msgstr "給 {0} 傳送訊息" - -#: src/components/dms/MessageMenu.tsx:72 -#: src/screens/Messages/List/ChatListItem.tsx:155 -msgid "Message deleted" -msgstr "訊息已刪除" - -#: src/view/com/posts/FeedErrorMessage.tsx:200 -msgid "Message from server: {0}" -msgstr "來自伺服器的訊息:{0}" - -#: src/screens/Messages/Conversation/MessageInput.tsx:138 -msgid "Message input field" -msgstr "訊息輸入欄位" - -#: src/screens/Messages/Conversation/MessageInput.tsx:70 -#: src/screens/Messages/Conversation/MessageInput.web.tsx:49 -msgid "Message is too long" -msgstr "訊息太長了" - -#: src/screens/Messages/List/index.tsx:321 -msgid "Message settings" -msgstr "訊息設定" - -#: src/Navigation.tsx:504 -#: src/screens/Messages/List/index.tsx:164 -#: src/screens/Messages/List/index.tsx:246 -#: src/screens/Messages/List/index.tsx:317 -msgid "Messages" -msgstr "訊息" - -#: src/lib/moderation/useReportOptions.ts:46 -msgid "Misleading Account" -msgstr "誤導性帳號" - -#: src/Navigation.tsx:125 -#: src/screens/Moderation/index.tsx:104 -#: src/view/screens/Settings/index.tsx:561 -msgid "Moderation" -msgstr "內容管理" - -#: src/components/moderation/ModerationDetailsDialog.tsx:112 -msgid "Moderation details" -msgstr "內容管理詳情" - -#: src/view/com/lists/ListCard.tsx:95 -#: src/view/com/modals/UserAddRemoveLists.tsx:217 -msgid "Moderation list by {0}" -msgstr "由 {0} 建立的內容管理列表" - -#: src/view/screens/ProfileList.tsx:843 -msgid "Moderation list by <0/>" -msgstr "由 建立的內容管理列表" - -#: src/view/com/lists/ListCard.tsx:93 -#: src/view/com/modals/UserAddRemoveLists.tsx:215 -#: src/view/screens/ProfileList.tsx:841 -msgid "Moderation list by you" -msgstr "您建立的內容管理列表" - -#: src/view/com/modals/CreateOrEditList.tsx:185 -msgid "Moderation list created" -msgstr "已建立內容管理列表" - -#: src/view/com/modals/CreateOrEditList.tsx:171 -msgid "Moderation list updated" -msgstr "內容管理列表已更新" - -#: src/screens/Moderation/index.tsx:243 -msgid "Moderation lists" -msgstr "內容管理列表" - -#: src/Navigation.tsx:130 -#: src/view/screens/ModerationModlists.tsx:58 -msgid "Moderation Lists" -msgstr "內容管理列表" - -#: src/view/screens/Settings/index.tsx:555 -msgid "Moderation settings" -msgstr "內容管理設定" - -#: src/Navigation.tsx:229 -msgid "Moderation states" -msgstr "內容管理狀態" - -#: src/screens/Moderation/index.tsx:215 -msgid "Moderation tools" -msgstr "內容管理工具" - -#: src/components/moderation/ModerationDetailsDialog.tsx:48 -#: src/lib/moderation/useModerationCauseDescription.ts:42 -msgid "Moderator has chosen to set a general warning on the content." -msgstr "內容管理者已將此內容標記為普通警告。" - -#: src/view/com/post-thread/PostThreadItem.tsx:567 -msgid "More" -msgstr "更多" - -#: src/view/shell/desktop/Feeds.tsx:55 -msgid "More feeds" -msgstr "更多動態源" - -#: src/view/screens/ProfileList.tsx:653 -msgid "More options" -msgstr "更多選項" - -#: src/view/screens/PreferencesThreads.tsx:82 -msgid "Most-liked replies first" -msgstr "最多喜歡數優先" - -#: src/components/TagMenu/index.tsx:249 -msgid "Mute" -msgstr "靜音" - -#: src/components/TagMenu/index.web.tsx:105 -msgid "Mute {truncatedTag}" -msgstr "靜音 {truncatedTag}" - -#: src/view/com/profile/ProfileMenu.tsx:284 -#: src/view/com/profile/ProfileMenu.tsx:291 -msgid "Mute Account" -msgstr "靜音帳號" - -#: src/view/screens/ProfileList.tsx:572 -msgid "Mute accounts" -msgstr "靜音帳號" - -#: src/components/TagMenu/index.tsx:209 -msgid "Mute all {displayTag} posts" -msgstr "將所有 {displayTag} 貼文靜音" - -#: src/components/dms/ConvoMenu.tsx:172 -#: src/components/dms/ConvoMenu.tsx:178 -msgid "Mute conversation" -msgstr "靜音對話" - -#: src/components/dialogs/MutedWords.tsx:148 -msgid "Mute in tags only" -msgstr "僅靜音標籤" - -#: src/components/dialogs/MutedWords.tsx:133 -msgid "Mute in text & tags" -msgstr "靜音文字和標籤" - -#: src/view/screens/ProfileList.tsx:678 -msgid "Mute list" -msgstr "靜音列表" - -#: src/view/screens/ProfileList.tsx:673 -msgid "Mute these accounts?" -msgstr "靜音這些帳號?" - -#: src/components/dialogs/MutedWords.tsx:126 -msgid "Mute this word in post text and tags" -msgstr "在貼文內容和話題標籤中隱藏該文字" - -#: src/components/dialogs/MutedWords.tsx:141 -msgid "Mute this word in tags only" -msgstr "僅在話題標籤中隱藏該文字" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:362 -#: src/view/com/util/forms/PostDropdownBtn.tsx:368 -msgid "Mute thread" -msgstr "靜音討論串" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:378 -#: src/view/com/util/forms/PostDropdownBtn.tsx:380 -msgid "Mute words & tags" -msgstr "靜音文字和標籤" - -#: src/view/com/lists/ListCard.tsx:104 -msgid "Muted" -msgstr "已靜音" - -#: src/screens/Moderation/index.tsx:255 -msgid "Muted accounts" -msgstr "已靜音帳號" - -#: src/Navigation.tsx:135 -#: src/view/screens/ModerationMutedAccounts.tsx:109 -msgid "Muted Accounts" -msgstr "已靜音帳號" - -#: src/view/screens/ModerationMutedAccounts.tsx:117 -msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private." -msgstr "已靜音的帳號將不會在您的通知或動態中顯示,靜音資訊是完全非公開的。" - -#: src/lib/moderation/useModerationCauseDescription.ts:87 -msgid "Muted by \"{0}\"" -msgstr "被「{0}」靜音" - -#: src/screens/Moderation/index.tsx:231 -msgid "Muted words & tags" -msgstr "靜音文字和標籤" - -#: src/view/screens/ProfileList.tsx:675 -msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." -msgstr "封鎖是私人的。被封鎖的帳號可以與您互動,但您將無法看到他們的貼文或收到來自他們的通知。" - -#: src/components/dialogs/BirthDateSettings.tsx:35 -#: src/components/dialogs/BirthDateSettings.tsx:38 -msgid "My Birthday" -msgstr "我的生日" - -#: src/view/screens/Feeds.tsx:734 -msgid "My Feeds" -msgstr "我的動態源" - -#: src/view/shell/desktop/LeftNav.tsx:84 -msgid "My Profile" -msgstr "我的個人檔案" - -#: src/view/screens/Settings/index.tsx:616 -msgid "My saved feeds" -msgstr "我儲存的動態源" - -#: src/view/screens/Settings/index.tsx:622 -msgid "My Saved Feeds" -msgstr "我儲存的動態源" - -#: src/view/com/modals/AddAppPasswords.tsx:173 -#: src/view/com/modals/CreateOrEditList.tsx:279 -msgid "Name" -msgstr "名稱" - -#: src/view/com/modals/CreateOrEditList.tsx:143 -msgid "Name is required" -msgstr "名稱是必填項" - -#: src/lib/moderation/useReportOptions.ts:58 -#: src/lib/moderation/useReportOptions.ts:92 -#: src/lib/moderation/useReportOptions.ts:100 -msgid "Name or Description Violates Community Standards" -msgstr "名稱或描述違反社群標準" - -#: src/screens/Onboarding/index.tsx:22 -msgid "Nature" -msgstr "自然" - -#: src/screens/Login/ForgotPasswordForm.tsx:173 -#: src/screens/Login/LoginForm.tsx:309 -#: src/view/com/modals/ChangePassword.tsx:169 -msgid "Navigates to the next screen" -msgstr "切換到下一畫面" - -#: src/view/shell/Drawer.tsx:79 -msgid "Navigates to your profile" -msgstr "切換到您的個人檔案" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:127 -msgid "Need to report a copyright violation?" -msgstr "需要檢舉侵權嗎?" - -#: src/screens/Onboarding/StepFinished.tsx:152 -msgid "Never lose access to your followers or data." -msgstr "永遠不會失去對您的跟隨者或資料的存取權。" - -#: src/view/com/modals/ChangeHandle.tsx:515 -msgid "Nevermind, create a handle for me" -msgstr "不用了,為我建立一個帳號代碼" - -#: src/view/screens/Lists.tsx:81 -msgctxt "action" -msgid "New" -msgstr "新增" - -#: src/view/screens/ModerationModlists.tsx:78 -msgid "New" -msgstr "新增" - -#: src/components/dms/dialogs/NewChatDialog.tsx:52 -#: src/screens/Messages/List/index.tsx:331 -#: src/screens/Messages/List/index.tsx:338 -msgid "New chat" -msgstr "新對話" - -#: src/components/dms/NewMessagesPill.tsx:92 -msgid "New messages" -msgstr "新訊息" - -#: src/view/com/modals/CreateOrEditList.tsx:241 -msgid "New Moderation List" -msgstr "新的內容管理列表" - -#: src/view/com/modals/ChangePassword.tsx:213 -msgid "New password" -msgstr "新密碼" - -#: src/view/com/modals/ChangePassword.tsx:218 -msgid "New Password" -msgstr "新密碼" - -#: src/view/com/feeds/FeedPage.tsx:147 -msgctxt "action" -msgid "New post" -msgstr "新貼文" - -#: src/view/screens/Feeds.tsx:566 -#: src/view/screens/Notifications.tsx:193 -#: src/view/screens/Profile.tsx:464 -#: src/view/screens/ProfileFeed.tsx:426 -#: src/view/screens/ProfileList.tsx:201 -#: src/view/screens/ProfileList.tsx:229 -#: src/view/shell/desktop/LeftNav.tsx:271 -msgid "New post" -msgstr "新貼文" - -#: src/view/shell/desktop/LeftNav.tsx:277 -msgctxt "action" -msgid "New Post" -msgstr "新貼文" - -#: src/components/NewskieDialog.tsx:68 -msgid "New user info dialog" -msgstr "新用戶資訊對話框" - -#: src/view/com/modals/CreateOrEditList.tsx:236 -msgid "New User List" -msgstr "新的用戶列表" - -#: src/view/screens/PreferencesThreads.tsx:79 -msgid "Newest replies first" -msgstr "最新回覆優先" - -#: src/screens/Onboarding/index.tsx:20 -msgid "News" -msgstr "新聞" - -#: src/screens/Login/ForgotPasswordForm.tsx:143 -#: src/screens/Login/ForgotPasswordForm.tsx:150 -#: src/screens/Login/LoginForm.tsx:308 -#: src/screens/Login/LoginForm.tsx:315 -#: src/screens/Login/SetNewPasswordForm.tsx:174 -#: src/screens/Login/SetNewPasswordForm.tsx:180 -#: src/screens/Signup/index.tsx:220 -#: src/view/com/modals/ChangePassword.tsx:254 -#: src/view/com/modals/ChangePassword.tsx:256 -msgid "Next" -msgstr "下一個" - -#: src/view/com/lightbox/Lightbox.web.tsx:169 -msgid "Next image" -msgstr "下一張圖片" - -#: src/view/screens/PreferencesFollowingFeed.tsx:128 -#: src/view/screens/PreferencesFollowingFeed.tsx:199 -#: src/view/screens/PreferencesFollowingFeed.tsx:234 -#: src/view/screens/PreferencesFollowingFeed.tsx:271 -#: src/view/screens/PreferencesThreads.tsx:106 -#: src/view/screens/PreferencesThreads.tsx:129 -msgid "No" -msgstr "關" - -#: src/view/screens/ProfileFeed.tsx:559 -#: src/view/screens/ProfileList.tsx:823 -msgid "No description" -msgstr "沒有描述" - -#: src/view/com/modals/ChangeHandle.tsx:399 -msgid "No DNS Panel" -msgstr "無 DNS 控制台" - -#: src/components/dialogs/GifSelect.ios.tsx:202 -#: src/components/dialogs/GifSelect.tsx:218 -msgid "No featured GIFs found. There may be an issue with Tenor." -msgstr "未找到精選 GIF,Tenor 可能發生問題。" - -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:120 -msgid "No longer following {0}" -msgstr "不再跟隨 {0}" - -#: src/screens/Signup/StepHandle.tsx:115 -msgid "No longer than 253 characters" -msgstr "不超過 253 個字符" - -#: src/screens/Messages/List/ChatListItem.tsx:106 -msgid "No messages yet" -msgstr "還沒有訊息" - -#: src/screens/Messages/List/index.tsx:274 -msgid "No more conversations to show" -msgstr "已經沒有對話啦!" - -#: src/view/com/notifications/Feed.tsx:118 -msgid "No notifications yet!" -msgstr "還沒有通知!" - -#: src/components/dms/MessagesNUX.tsx:149 -#: src/components/dms/MessagesNUX.tsx:152 -#: src/screens/Messages/Settings.tsx:93 -#: src/screens/Messages/Settings.tsx:96 -msgid "No one" -msgstr "沒有人" - -#: src/screens/Profile/Sections/Feed.tsx:59 -msgid "No posts yet." -msgstr "目前還沒有貼文。" - -#: src/view/com/composer/text-input/mobile/Autocomplete.tsx:101 -#: src/view/com/composer/text-input/web/Autocomplete.tsx:195 -msgid "No result" -msgstr "沒有結果" - -#: src/components/dms/dialogs/SearchablePeopleList.tsx:202 -msgid "No results" -msgstr "沒有結果" - -#: src/components/Lists.tsx:207 -msgid "No results found" -msgstr "未找到結果" - -#: src/view/screens/Feeds.tsx:497 -msgid "No results found for \"{query}\"" -msgstr "未找到「{query}」的結果" - -#: src/view/com/modals/ListAddRemoveUsers.tsx:127 -#: src/view/screens/Search/Search.tsx:233 -#: src/view/screens/Search/Search.tsx:272 -#: src/view/screens/Search/Search.tsx:318 -msgid "No results found for {query}" -msgstr "未找到 {query} 的結果" - -#: src/components/dialogs/GifSelect.ios.tsx:200 -#: src/components/dialogs/GifSelect.tsx:216 -msgid "No search results found for \"{search}\"." -msgstr "未找到「{search}」的搜尋結果。" - -#: src/components/dialogs/EmbedConsent.tsx:105 -#: src/components/dialogs/EmbedConsent.tsx:112 -msgid "No thanks" -msgstr "不,謝謝" - -#: src/view/com/modals/Threadgate.tsx:85 -msgid "Nobody" -msgstr "沒有人" - -#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:48 -msgid "Nobody can reply" -msgstr "沒有人可以回覆" - -#: src/components/LikedByList.tsx:79 -#: src/components/LikesDialog.tsx:99 -msgid "Nobody has liked this yet. Maybe you should be the first!" -msgstr "還沒有人按喜歡,也許您應該成為第一個!" - -#: src/lib/moderation/useGlobalLabelStrings.ts:42 -msgid "Non-sexual Nudity" -msgstr "非色情內容裸體" - -#: src/Navigation.tsx:115 -#: src/view/screens/Profile.tsx:100 -msgid "Not Found" -msgstr "未找到" - -#: src/view/com/modals/VerifyEmail.tsx:254 -#: src/view/com/modals/VerifyEmail.tsx:260 -msgid "Not right now" -msgstr "暫時不需要" - -#: src/view/com/profile/ProfileMenu.tsx:373 -#: src/view/com/util/forms/PostDropdownBtn.tsx:456 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:311 -msgid "Note about sharing" -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." -msgstr "注意:Bluesky 是一個開放且公開的網路。此設定僅限制您在 Bluesky 應用程式和網站上的內容可見性,其他應用程式可能不會遵循這個規則。您的內容仍可能由其他應用程式和網站顯示給未登入的使用者。" - -#: src/screens/Messages/List/index.tsx:215 -msgid "Nothing here" -msgstr "這裡什麼也沒有" - -#: src/screens/Messages/Settings.tsx:124 -msgid "Notification sounds" -msgstr "通知音效" - -#: src/screens/Messages/Settings.tsx:121 -msgid "Notification Sounds" -msgstr "通知音效" - -#: src/Navigation.tsx:499 -#: src/view/screens/Notifications.tsx:132 -#: src/view/screens/Notifications.tsx:169 -#: src/view/shell/bottom-bar/BottomBar.tsx:227 -#: src/view/shell/desktop/LeftNav.tsx:350 -#: src/view/shell/Drawer.tsx:457 -#: src/view/shell/Drawer.tsx:458 -msgid "Notifications" -msgstr "通知" - -#: src/lib/hooks/useTimeAgo.ts:51 -msgid "now" -msgstr "現在" - -#: src/components/dms/MessageItem.tsx:175 -msgid "Now" -msgstr "現在" - -#: src/view/com/modals/SelfLabel.tsx:104 -msgid "Nudity" -msgstr "裸露" - -#: src/lib/moderation/useReportOptions.ts:72 -msgid "Nudity or adult content not labeled as such" -msgstr "未貼上此類標記的裸露或成人內容" - -#: src/lib/moderation/useLabelBehaviorDescription.ts:11 -msgid "Off" -msgstr "顯示" - -#: src/components/dialogs/GifSelect.ios.tsx:237 -#: src/components/dialogs/GifSelect.tsx:255 -#: src/view/com/util/ErrorBoundary.tsx:55 -msgid "Oh no!" -msgstr "糟糕!" - -#: src/screens/Onboarding/StepInterests/index.tsx:133 -msgid "Oh no! Something went wrong." -msgstr "糟糕!發生了一些錯誤。" - -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:336 -msgid "OK" -msgstr "好的" - -#: src/screens/Login/PasswordUpdatedForm.tsx:44 -msgid "Okay" -msgstr "好的" - -#: src/view/screens/PreferencesThreads.tsx:78 -msgid "Oldest replies first" -msgstr "最舊的回覆優先" - -#: src/lib/hooks/useTimeAgo.ts:81 -msgid "on {str}" -msgstr "在 {str}" - -#: src/view/screens/Settings/index.tsx:256 -msgid "Onboarding reset" -msgstr "重新開始引導流程" - -#: src/view/com/composer/Composer.tsx:505 -msgid "One or more images is missing alt text." -msgstr "至少有一張圖片缺失了替代文字。" - -#: src/screens/Onboarding/StepProfile/index.tsx:117 -msgid "Only .jpg and .png files are supported" -msgstr "僅支援 .jpg 或 .png 格式的圖片" - -#: src/view/com/threadgate/WhoCanReply.tsx:239 -msgid "Only {0} can reply" -msgstr "只有{0}可以回覆" - -#: src/screens/Signup/StepHandle.tsx:98 -msgid "Only contains letters, numbers, and hyphens" -msgstr "只包含字母、數字和連字符" - -#: src/components/Lists.tsx:88 -msgid "Oops, something went wrong!" -msgstr "糟糕,發生了錯誤!" - -#: src/components/Lists.tsx:191 -#: src/view/screens/AppPasswords.tsx:69 -#: src/view/screens/Profile.tsx:100 -msgid "Oops!" -msgstr "糟糕!" - -#: src/screens/Onboarding/StepFinished.tsx:148 -msgid "Open" -msgstr "開啟" - -#: src/view/com/posts/AviFollowButton.tsx:89 -msgid "Open {name} profile shortcut menu" -msgstr "開啟 {name} 個人檔案快捷選單" - -#: src/screens/Onboarding/StepProfile/index.tsx:277 -msgid "Open avatar creator" -msgstr "開啟頭像建立工具" - -#: src/screens/Messages/List/ChatListItem.tsx:219 -#: src/screens/Messages/List/ChatListItem.tsx:220 -msgid "Open conversation options" -msgstr "開啟對話選項" - -#: src/view/com/composer/Composer.tsx:615 -#: src/view/com/composer/Composer.tsx:616 -msgid "Open emoji picker" -msgstr "開啟表情符號選擇器" - -#: src/view/screens/ProfileFeed.tsx:295 -msgid "Open feed options menu" -msgstr "開啟動態選項選單" - -#: src/view/screens/Settings/index.tsx:736 -msgid "Open links with in-app browser" -msgstr "在內建瀏覽器中開啟連結" - -#: src/components/dms/ActionsWrapper.tsx:87 -msgid "Open message options" -msgstr "開啟訊息選項" - -#: src/screens/Moderation/index.tsx:227 -msgid "Open muted words and tags settings" -msgstr "開啟靜音文字和標籤設定" - -#: src/view/com/home/HomeHeaderLayoutMobile.tsx:54 -msgid "Open navigation" -msgstr "開啟導覽" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:247 -msgid "Open post options menu" -msgstr "開啟貼文選項選單" - -#: src/view/screens/Settings/index.tsx:860 -#: src/view/screens/Settings/index.tsx:870 -msgid "Open storybook page" -msgstr "開啟故事書頁面" - -#: src/view/screens/Settings/index.tsx:848 -msgid "Open system log" -msgstr "開啟系統日誌" - -#: src/view/com/util/forms/DropdownButton.tsx:159 -msgid "Opens {numItems} options" -msgstr "開啟 {numItems} 個選項" - -#: src/view/screens/Settings/index.tsx:510 -msgid "Opens accessibility settings" -msgstr "開啟無障礙設定" - -#: src/view/screens/Log.tsx:58 -msgid "Opens additional details for a debug entry" -msgstr "開啟除錯項目的額外詳細資訊" - -#: src/view/com/composer/photos/OpenCameraBtn.tsx:74 -msgid "Opens camera on device" -msgstr "開啟裝置相機" - -#: src/view/screens/Settings/index.tsx:639 -msgid "Opens chat settings" -msgstr "開啟對話設定" - -#: src/view/com/composer/Prompt.tsx:27 -msgid "Opens composer" -msgstr "開啟編輯器" - -#: src/view/screens/Settings/index.tsx:531 -msgid "Opens configurable language settings" -msgstr "開啟可以更改的語言設定" - -#: src/view/com/composer/photos/SelectPhotoBtn.tsx:40 -msgid "Opens device photo gallery" -msgstr "開啟裝置相簿" - -#: src/view/screens/Settings/index.tsx:671 -msgid "Opens external embeds settings" -msgstr "開啟外部連結嵌入設定" - -#: src/view/com/auth/SplashScreen.tsx:50 -#: src/view/com/auth/SplashScreen.web.tsx:99 -msgid "Opens flow to create a new Bluesky account" -msgstr "開始建立新的 Bluesky 帳號的流程" - -#: src/view/com/auth/SplashScreen.tsx:65 -#: src/view/com/auth/SplashScreen.web.tsx:114 -msgid "Opens flow to sign into your existing Bluesky account" -msgstr "開始登入您現有的 Bluesky 帳號流程" - -#: src/view/com/composer/photos/SelectGifBtn.tsx:36 -msgid "Opens GIF select dialog" -msgstr "開啟 GIF 選擇對話框" - -#: src/view/com/modals/InviteCodes.tsx:173 -msgid "Opens list of invite codes" -msgstr "開啟邀請碼列表" - -#: src/view/screens/Settings/index.tsx:808 -msgid "Opens modal for account deactivation confirmation" -msgstr "開啟帳號刪除的確認彈窗" - -#: src/view/screens/Settings/index.tsx:830 -msgid "Opens modal for account deletion confirmation. Requires email code" -msgstr "開啟帳號刪除的確認彈窗。需要電子郵件驗證碼" - -#: src/view/screens/Settings/index.tsx:765 -msgid "Opens modal for changing your Bluesky password" -msgstr "開啟修改 Bluesky 密碼的彈窗" - -#: src/view/screens/Settings/index.tsx:720 -msgid "Opens modal for choosing a new Bluesky handle" -msgstr "開啟建立新 Bluesky 帳號代碼的彈窗" - -#: src/view/screens/Settings/index.tsx:788 -msgid "Opens modal for downloading your Bluesky account data (repository)" -msgstr "開啟下載 Bluesky 帳號數據(存儲庫)的彈窗" - -#: src/view/screens/Settings/index.tsx:1008 -msgid "Opens modal for email verification" -msgstr "開啟驗證電子郵件的彈窗" - -#: src/view/com/modals/ChangeHandle.tsx:276 -msgid "Opens modal for using custom domain" -msgstr "開啟使用自訂網域的彈窗" - -#: src/view/screens/Settings/index.tsx:556 -msgid "Opens moderation settings" -msgstr "開啟內容管理設定" - -#: src/screens/Login/LoginForm.tsx:225 -msgid "Opens password reset form" -msgstr "開啟密碼重設表單" - -#: src/view/screens/Settings/index.tsx:617 -msgid "Opens screen with all saved feeds" -msgstr "開啟包含所有已儲存的動態源之畫面" - -#: src/view/screens/Settings/index.tsx:698 -msgid "Opens the app password settings" -msgstr "開啟應用程式專用密碼設定畫面" - -#: src/view/screens/Settings/index.tsx:574 -msgid "Opens the Following feed preferences" -msgstr "開啟「Following」動態源偏好" - -#: src/view/com/modals/LinkWarning.tsx:93 -msgid "Opens the linked website" -msgstr "開啟網站連結" - -#: src/view/screens/Settings/index.tsx:861 -#: src/view/screens/Settings/index.tsx:871 -msgid "Opens the storybook page" -msgstr "開啟故事書頁面" - -#: src/view/screens/Settings/index.tsx:849 -msgid "Opens the system log page" -msgstr "開啟系統日誌頁面" - -#: src/view/screens/Settings/index.tsx:595 -msgid "Opens the threads preferences" -msgstr "開啟討論串偏好" - -#: src/view/com/notifications/FeedItem.tsx:429 -#: src/view/com/util/UserAvatar.tsx:422 -msgid "Opens this profile" -msgstr "開啟這個個人檔案" - -#: src/view/com/util/forms/DropdownButton.tsx:293 -msgid "Option {0} of {numItems}" -msgstr "{0} 選項,共 {numItems} 個" - -#: src/components/dms/ReportDialog.tsx:183 -#: src/components/ReportDialog/SubmitView.tsx:162 -msgid "Optionally provide additional information below:" -msgstr "在以下提供額外訊息(可選):" - -#: src/view/com/modals/Threadgate.tsx:92 -msgid "Or combine these options:" -msgstr "或者組合這些選項:" - -#: src/screens/Deactivated.tsx:211 -msgid "Or, continue with another account." -msgstr "或以其他帳號繼續。" - -#: src/screens/Deactivated.tsx:194 -msgid "Or, log into one of your other accounts." -msgstr "或登入您的其他帳號。" - -#: src/lib/moderation/useReportOptions.ts:26 -msgid "Other" -msgstr "其他" - -#: src/components/AccountList.tsx:76 -msgid "Other account" -msgstr "其他帳號" - -#: src/view/com/composer/select-language/SelectLangBtn.tsx:92 -msgid "Other..." -msgstr "其他…" - -#: src/screens/Messages/Conversation/ChatDisabled.tsx:28 -msgid "Our moderators have reviewed reports and decided to disable your access to chats on Bluesky." -msgstr "我們的內容管理者已審核檢舉,並決定停用您在 Bluesky 上的對話功能。" - -#: src/components/Lists.tsx:208 -#: src/view/screens/NotFound.tsx:45 -msgid "Page not found" -msgstr "頁面不存在" - -#: src/view/screens/NotFound.tsx:42 -msgid "Page Not Found" -msgstr "頁面不存在" - -#: src/screens/Login/LoginForm.tsx:201 -#: src/screens/Signup/StepInfo/index.tsx:102 -#: src/view/com/modals/DeleteAccount.tsx:257 -#: src/view/com/modals/DeleteAccount.tsx:264 -msgid "Password" -msgstr "密碼" - -#: src/view/com/modals/ChangePassword.tsx:143 -msgid "Password Changed" -msgstr "密碼已更改" - -#: src/screens/Login/index.tsx:157 -msgid "Password updated" -msgstr "密碼已更新" - -#: src/screens/Login/PasswordUpdatedForm.tsx:30 -msgid "Password updated!" -msgstr "密碼已更新!" - -#: src/view/com/util/post-embeds/GifEmbed.tsx:37 -msgid "Pause" -msgstr "暫停" - -#: src/view/screens/Search/Search.tsx:369 -msgid "People" -msgstr "用戶" - -#: src/Navigation.tsx:170 -msgid "People followed by @{0}" -msgstr "被 @{0} 跟隨的人" - -#: src/Navigation.tsx:163 -msgid "People following @{0}" -msgstr "跟隨 @{0} 的人" - -#: src/view/com/lightbox/Lightbox.tsx:67 -msgid "Permission to access camera roll is required." -msgstr "需要相簿權限。" - -#: src/view/com/lightbox/Lightbox.tsx:73 -msgid "Permission to access camera roll was denied. Please enable it in your system settings." -msgstr "相簿權限已遭拒絕,請在系統設定中啟用。" - -#: src/screens/Onboarding/index.tsx:28 -msgid "Pets" -msgstr "寵物" - -#: src/view/com/modals/SelfLabel.tsx:122 -msgid "Pictures meant for adults." -msgstr "適合成年人的圖像。" - -#: src/view/screens/ProfileFeed.tsx:287 -#: src/view/screens/ProfileList.tsx:617 -msgid "Pin to home" -msgstr "釘選到首頁" - -#: src/view/screens/ProfileFeed.tsx:290 -msgid "Pin to Home" -msgstr "釘選到首頁" - -#: src/view/screens/SavedFeeds.tsx:103 -msgid "Pinned Feeds" -msgstr "釘選的動態源列表" - -#: src/view/screens/ProfileList.tsx:289 -msgid "Pinned to your feeds" -msgstr "從您的動態中取消釘選" - -#: src/view/com/util/post-embeds/GifEmbed.tsx:37 -msgid "Play" -msgstr "播放" - -#: src/view/com/util/post-embeds/ExternalGifEmbed.tsx:123 -msgid "Play {0}" -msgstr "播放 {0}" - -#: src/view/com/util/post-embeds/GifEmbed.tsx:36 -msgid "Play or pause the GIF" -msgstr "播放或暫停 GIF" - -#: src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx:57 -#: src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx:58 -msgid "Play Video" -msgstr "播放影片" - -#: src/view/com/util/post-embeds/ExternalGifEmbed.tsx:122 -msgid "Plays the GIF" -msgstr "播放 GIF" - -#: src/screens/Signup/state.ts:234 -msgid "Please choose your handle." -msgstr "請設定您的帳號代碼。" - -#: src/screens/Signup/state.ts:227 -msgid "Please choose your password." -msgstr "請設定您的密碼。" - -#: src/screens/Signup/state.ts:248 -msgid "Please complete the verification captcha." -msgstr "請完成 Captcha 驗證。" - -#: src/view/com/modals/ChangeEmail.tsx:65 -msgid "Please confirm your email before changing it. This is a temporary requirement while email-updating tools are added, and it will soon be removed." -msgstr "更改前請先確認您的電子郵件地址。這是電子郵件更新工具的臨時要求,此限制很快就會被移除。" - -#: src/view/com/modals/AddAppPasswords.tsx:94 -msgid "Please enter a name for your app password. All spaces is not allowed." -msgstr "請輸入應用程式專用密碼的名稱。不允許包含任何空格。" - -#: src/view/com/modals/AddAppPasswords.tsx:150 -msgid "Please enter a unique name for this App Password or use our randomly generated one." -msgstr "請輸入此應用程式專用密碼的唯一名稱,或使用我們提供的隨機生成名稱。" - -#: src/components/dialogs/MutedWords.tsx:67 -msgid "Please enter a valid word, tag, or phrase to mute" -msgstr "請輸入有效的文字或標籤進行靜音" - -#: src/screens/Signup/state.ts:213 -msgid "Please enter your email." -msgstr "請輸入您的電子郵件。" - -#: src/view/com/modals/DeleteAccount.tsx:253 -msgid "Please enter your password as well:" -msgstr "請輸入您的密碼:" - -#: src/components/moderation/LabelsOnMeDialog.tsx:256 -msgid "Please explain why you think this label was incorrectly applied by {0}" -msgstr "請解釋您認為 {0} 不該套用此標記的原因" - -#: src/screens/Messages/Conversation/ChatDisabled.tsx:110 -msgid "Please explain why you think your chats were incorrectly disabled" -msgstr "請解釋您認為我們不該停用您對話功能的原因" - -#: src/lib/hooks/useAccountSwitcher.ts:48 -#: src/lib/hooks/useAccountSwitcher.ts:58 -msgid "Please sign in as @{0}" -msgstr "請以 @{0} 的身分登入" - -#: src/view/com/modals/VerifyEmail.tsx:109 -msgid "Please Verify Your Email" -msgstr "請驗證您的電子郵件地址" - -#: src/view/com/composer/Composer.tsx:281 -msgid "Please wait for your link card to finish loading" -msgstr "請等待您的連結預覽載入完畢" - -#: src/screens/Onboarding/index.tsx:34 -msgid "Politics" -msgstr "政治" - -#: src/view/com/modals/SelfLabel.tsx:112 -msgid "Porn" -msgstr "色情內容" - -#: src/view/com/composer/Composer.tsx:479 -#: src/view/com/composer/Composer.tsx:487 -msgctxt "action" -msgid "Post" -msgstr "發佈" - -#: src/view/com/post-thread/PostThread.tsx:434 -msgctxt "description" -msgid "Post" -msgstr "貼文" - -#: src/view/com/post-thread/PostThreadItem.tsx:189 -msgid "Post by {0}" -msgstr "{0} 的貼文" - -#: src/Navigation.tsx:189 -#: src/Navigation.tsx:196 -#: src/Navigation.tsx:203 -msgid "Post by @{0}" -msgstr "@{0} 的貼文" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:132 -msgid "Post deleted" -msgstr "貼文已刪除" - -#: src/view/com/post-thread/PostThread.tsx:193 -msgid "Post hidden" -msgstr "貼文已隱藏" - -#: src/components/moderation/ModerationDetailsDialog.tsx:97 -#: src/lib/moderation/useModerationCauseDescription.ts:101 -msgid "Post Hidden by Muted Word" -msgstr "貼文因靜音文字而被隱藏" - -#: src/components/moderation/ModerationDetailsDialog.tsx:100 -#: src/lib/moderation/useModerationCauseDescription.ts:110 -msgid "Post Hidden by You" -msgstr "被您靜音的貼文" - -#: src/view/com/composer/select-language/SelectLangBtn.tsx:88 -msgid "Post language" -msgstr "貼文語言" - -#: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:75 -msgid "Post Languages" -msgstr "貼文語言" - -#: src/view/com/post-thread/PostThread.tsx:188 -#: src/view/com/post-thread/PostThread.tsx:200 -msgid "Post not found" -msgstr "找不到貼文" - -#: src/components/TagMenu/index.tsx:253 -msgid "posts" -msgstr "貼文" - -#: src/view/screens/Profile.tsx:193 -msgid "Posts" -msgstr "貼文" - -#: src/components/dialogs/MutedWords.tsx:89 -msgid "Posts can be muted based on their text, their tags, or both." -msgstr "可以靜音貼文所包含的文字和標籤。" - -#: src/view/com/posts/FeedErrorMessage.tsx:68 -msgid "Posts hidden" -msgstr "貼文已隱藏" - -#: src/view/com/modals/LinkWarning.tsx:60 -msgid "Potentially Misleading Link" -msgstr "潛在誤導性連結" - -#: src/screens/Messages/Conversation/MessageListError.tsx:19 -msgid "Press to attempt reconnection" -msgstr "點擊以重試連線" - -#: src/components/forms/HostingProvider.tsx:46 -msgid "Press to change hosting provider" -msgstr "按下以更改託管服務供應商" - -#: src/components/Error.tsx:85 -#: src/components/Lists.tsx:93 -#: src/screens/Messages/Conversation/MessageListError.tsx:24 -#: src/screens/Signup/index.tsx:200 -msgid "Press to retry" -msgstr "按下以重試" - -#: src/components/KnownFollowers.tsx:116 -msgid "Press to view followers of this account that you also follow" -msgstr "按下以查看哪些您認識的人跟隨了此帳號" - -#: src/view/com/lightbox/Lightbox.web.tsx:150 -msgid "Previous image" -msgstr "上一張圖片" - -#: src/view/screens/LanguageSettings.tsx:189 -msgid "Primary Language" -msgstr "主要語言" - -#: src/view/screens/PreferencesThreads.tsx:97 -msgid "Prioritize Your Follows" -msgstr "優先顯示跟隨者" - -#: src/view/screens/Settings/index.tsx:654 -#: src/view/shell/desktop/RightNav.tsx:77 -msgid "Privacy" -msgstr "隱私" - -#: src/Navigation.tsx:244 -#: src/screens/Signup/StepInfo/Policies.tsx:56 -#: src/view/screens/PrivacyPolicy.tsx:29 -#: src/view/screens/Settings/index.tsx:957 -#: src/view/shell/Drawer.tsx:285 -msgid "Privacy Policy" -msgstr "隱私政策" - -#: src/components/dms/MessagesNUX.tsx:91 -msgid "Privately chat with other users." -msgstr "和其他用戶進行私人對話。" - -#: src/screens/Login/ForgotPasswordForm.tsx:156 -msgid "Processing..." -msgstr "處理中…" - -#: src/view/screens/DebugMod.tsx:894 -#: src/view/screens/Profile.tsx:345 -msgid "profile" -msgstr "個人檔案" - -#: src/view/shell/bottom-bar/BottomBar.tsx:272 -#: src/view/shell/desktop/LeftNav.tsx:381 -#: src/view/shell/Drawer.tsx:78 -#: src/view/shell/Drawer.tsx:542 -#: src/view/shell/Drawer.tsx:543 -msgid "Profile" -msgstr "個人檔案" - -#: src/view/com/modals/EditProfile.tsx:129 -msgid "Profile updated" -msgstr "個人檔案已更新" - -#: src/view/screens/Settings/index.tsx:1021 -msgid "Protect your account by verifying your email." -msgstr "通過驗證電子郵件地址來保護您的帳號。" - -#: src/screens/Onboarding/StepFinished.tsx:134 -msgid "Public" -msgstr "公開內容" - -#: src/view/screens/ModerationModlists.tsx:61 -msgid "Public, shareable lists of users to mute or block in bulk." -msgstr "公開且可共享的批量靜音或封鎖列表。" - -#: src/view/screens/Lists.tsx:66 -msgid "Public, shareable lists which can drive feeds." -msgstr "公開且可共享的列表,可作為動態源使用。" - -#: src/view/com/composer/Composer.tsx:464 -msgid "Publish post" -msgstr "發佈貼文" - -#: src/view/com/composer/Composer.tsx:464 -msgid "Publish reply" -msgstr "發佈回覆" - -#: src/view/com/util/post-ctrls/RepostButton.tsx:116 -#: src/view/com/util/post-ctrls/RepostButton.tsx:128 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:79 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:82 -msgid "Quote post" -msgstr "引用貼文" - -#: src/view/screens/PreferencesThreads.tsx:86 -msgid "Random (aka \"Poster's Roulette\")" -msgstr "隨機顯示 (又名試試手氣)" - -#: src/view/com/modals/EditImage.tsx:237 -msgid "Ratios" -msgstr "比率" - -#: src/screens/Deactivated.tsx:144 -msgid "Reactivate your account" -msgstr "重新啟用您的帳號" - -#: src/components/dms/ReportDialog.tsx:174 -msgid "Reason:" -msgstr "原因:" - -#: src/view/screens/Search/Search.tsx:933 -msgid "Recent Searches" -msgstr "最近的搜尋結果" - -#: src/screens/Messages/Conversation/MessageListError.tsx:20 -msgid "Reconnect" -msgstr "重新連線" - -#: src/screens/Messages/List/index.tsx:200 -msgid "Reload conversations" -msgstr "重新載入對話" - -#: src/components/dialogs/MutedWords.tsx:286 -#: src/components/FeedCard.tsx:200 -#: src/view/com/feeds/FeedSourceCard.tsx:317 -#: src/view/com/modals/ListAddRemoveUsers.tsx:268 -#: src/view/com/modals/SelfLabel.tsx:84 -#: src/view/com/modals/UserAddRemoveLists.tsx:230 -#: src/view/com/posts/FeedErrorMessage.tsx:212 -msgid "Remove" -msgstr "刪除" - -#: src/view/com/util/AccountDropdownBtn.tsx:22 -msgid "Remove account" -msgstr "刪除帳號" - -#: src/view/com/util/UserAvatar.tsx:384 -msgid "Remove Avatar" -msgstr "刪除頭像" - -#: src/view/com/util/UserBanner.tsx:155 -msgid "Remove Banner" -msgstr "刪除橫幅" - -#: src/screens/Messages/Conversation/MessageInputEmbed.tsx:218 -msgid "Remove embed" -msgstr "刪除嵌入" - -#: src/view/com/posts/FeedErrorMessage.tsx:168 -#: src/view/com/posts/FeedShutdownMsg.tsx:113 -#: src/view/com/posts/FeedShutdownMsg.tsx:117 -msgid "Remove feed" -msgstr "刪除動態源" - -#: src/view/com/posts/FeedErrorMessage.tsx:209 -msgid "Remove feed?" -msgstr "刪除動態源?" - -#: src/view/com/feeds/FeedSourceCard.tsx:188 -#: src/view/com/feeds/FeedSourceCard.tsx:266 -#: src/view/screens/ProfileFeed.tsx:330 -#: src/view/screens/ProfileFeed.tsx:336 -#: src/view/screens/ProfileList.tsx:443 -msgid "Remove from my feeds" -msgstr "從我的動態源中刪除" - -#: src/components/FeedCard.tsx:195 -#: src/view/com/feeds/FeedSourceCard.tsx:312 -msgid "Remove from my feeds?" -msgstr "從我的動態源中刪除?" - -#: src/view/com/composer/photos/Gallery.tsx:174 -msgid "Remove image" -msgstr "刪除圖片" - -#: src/view/com/composer/ExternalEmbed.tsx:87 -msgid "Remove image preview" -msgstr "刪除圖片預覽" - -#: src/components/dialogs/MutedWords.tsx:329 -msgid "Remove mute word from your list" -msgstr "從您的列表中刪除靜音文字" - -#: src/view/screens/Search/Search.tsx:974 -msgid "Remove profile" -msgstr "刪除個人檔案" - -#: src/view/screens/Search/Search.tsx:976 -msgid "Remove profile from search history" -msgstr "刪除搜尋紀錄中的個人檔案" - -#: src/view/com/util/post-embeds/QuoteEmbed.tsx:238 -msgid "Remove quote" -msgstr "刪除引用貼文" - -#: src/view/com/util/post-ctrls/RepostButton.tsx:93 -#: src/view/com/util/post-ctrls/RepostButton.tsx:109 -msgid "Remove repost" -msgstr "刪除轉貼貼文" - -#: src/view/com/posts/FeedErrorMessage.tsx:210 -msgid "Remove this feed from your saved feeds" -msgstr "將這個動態源從您已儲存之動態源列表中刪除" - -#: src/view/com/modals/ListAddRemoveUsers.tsx:199 -#: src/view/com/modals/UserAddRemoveLists.tsx:165 -msgid "Removed from list" -msgstr "從列表中刪除" - -#: src/view/com/feeds/FeedSourceCard.tsx:139 -msgid "Removed from my feeds" -msgstr "已從我的動態源中刪除" - -#: src/view/com/posts/FeedShutdownMsg.tsx:44 -#: src/view/screens/ProfileFeed.tsx:191 -#: src/view/screens/ProfileList.tsx:320 -msgid "Removed from your feeds" -msgstr "從您的動態中刪除" - -#: src/view/com/composer/ExternalEmbed.tsx:88 -msgid "Removes default thumbnail from {0}" -msgstr "從 {0} 中刪除預設縮圖" - -#: src/view/com/util/post-embeds/QuoteEmbed.tsx:239 -msgid "Removes quoted post" -msgstr "刪除已轉貼貼文" - -#: src/view/com/posts/FeedShutdownMsg.tsx:126 -#: src/view/com/posts/FeedShutdownMsg.tsx:130 -msgid "Replace with Discover" -msgstr "用「Discover」動態源取代" - -#: src/view/screens/Profile.tsx:194 -msgid "Replies" -msgstr "回覆" - -#: src/view/com/threadgate/WhoCanReply.tsx:66 -msgid "Replies disabled" -msgstr "回覆已被關閉" - -#: src/view/com/threadgate/WhoCanReply.tsx:123 -msgid "Replies on this thread are disabled" -msgstr "此討論串的回覆已停用" - -#: src/view/com/threadgate/WhoCanReply.tsx:237 -msgid "Replies to this thread are disabled" -msgstr "此討論串的回覆已停用。" - -#: src/view/com/composer/Composer.tsx:477 -msgctxt "action" -msgid "Reply" -msgstr "回覆" - -#: src/view/screens/PreferencesFollowingFeed.tsx:143 -msgid "Reply Filters" -msgstr "回覆過濾器" - -#: src/view/com/post/Post.tsx:190 -#: src/view/com/posts/FeedItem.tsx:439 -msgctxt "description" -msgid "Reply to <0><1/>" -msgstr "對 <0><1/> 回覆" - -#: src/view/com/posts/FeedItem.tsx:437 -msgctxt "description" -msgid "Reply to a blocked post" -msgstr "對已被封鎖的貼文回覆" - -#: src/components/dms/MessageMenu.tsx:132 -#: src/components/dms/MessagesListBlockedFooter.tsx:77 -#: src/components/dms/MessagesListBlockedFooter.tsx:84 -msgid "Report" -msgstr "檢舉" - -#: src/view/com/profile/ProfileMenu.tsx:324 -#: src/view/com/profile/ProfileMenu.tsx:327 -msgid "Report Account" -msgstr "檢舉帳號" - -#: src/components/dms/ConvoMenu.tsx:197 -#: src/components/dms/ConvoMenu.tsx:200 -#: src/components/dms/ReportConversationPrompt.tsx:18 -msgid "Report conversation" -msgstr "檢舉對話" - -#: src/components/ReportDialog/index.tsx:49 -msgid "Report dialog" -msgstr "檢舉對話框" - -#: src/view/screens/ProfileFeed.tsx:347 -#: src/view/screens/ProfileFeed.tsx:349 -msgid "Report feed" -msgstr "檢舉動態源" - -#: src/view/screens/ProfileList.tsx:485 -msgid "Report List" -msgstr "檢舉列表" - -#: src/components/dms/MessageMenu.tsx:130 -msgid "Report message" -msgstr "檢舉訊息" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:404 -#: src/view/com/util/forms/PostDropdownBtn.tsx:406 -msgid "Report post" -msgstr "檢舉貼文" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:43 -msgid "Report this content" -msgstr "檢舉這個內容" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:56 -msgid "Report this feed" -msgstr "檢舉這個動態源" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:53 -msgid "Report this list" -msgstr "檢舉這個列表" - -#: src/components/dms/ReportDialog.tsx:48 -#: src/components/dms/ReportDialog.tsx:142 -#: src/components/ReportDialog/SelectReportOptionView.tsx:59 -msgid "Report this message" -msgstr "檢舉這個訊息" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:50 -msgid "Report this post" -msgstr "檢舉這則貼文" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:47 -msgid "Report this user" -msgstr "檢舉這個用戶" - -#: src/view/com/util/post-ctrls/RepostButton.tsx:65 -#: src/view/com/util/post-ctrls/RepostButton.tsx:94 -#: src/view/com/util/post-ctrls/RepostButton.tsx:110 -msgctxt "action" -msgid "Repost" -msgstr "轉貼" - -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:70 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:74 -msgid "Repost" -msgstr "轉貼" - -#: src/view/com/util/post-ctrls/RepostButton.tsx:86 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:47 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:93 -msgid "Repost or quote post" -msgstr "轉貼或引用貼文" - -#: src/view/screens/PostRepostedBy.tsx:27 -msgid "Reposted By" -msgstr "轉貼" - -#: src/view/com/posts/FeedItem.tsx:254 -msgid "Reposted by {0}" -msgstr "由 {0} 轉貼" - -#: src/view/com/posts/FeedItem.tsx:269 -msgid "Reposted by <0><1/>" -msgstr "由 <0><1/> 轉貼" - -#: src/view/com/notifications/FeedItem.tsx:172 -msgid "reposted your post" -msgstr "轉貼您的貼文" - -#: src/view/com/post-thread/PostThreadItem.tsx:202 -msgid "Reposts of this post" -msgstr "轉貼這則貼文" - -#: src/view/com/modals/ChangeEmail.tsx:176 -#: src/view/com/modals/ChangeEmail.tsx:178 -msgid "Request Change" -msgstr "請求變更" - -#: src/view/com/modals/ChangePassword.tsx:242 -#: src/view/com/modals/ChangePassword.tsx:244 -msgid "Request Code" -msgstr "請求代碼" - -#: src/view/screens/AccessibilitySettings.tsx:88 -msgid "Require alt text before posting" -msgstr "要求發佈前提供替代文字" - -#: src/view/screens/Settings/Email2FAToggle.tsx:51 -msgid "Require email code to log into your account" -msgstr "登入時要求電子郵件驗證碼" - -#: src/screens/Signup/StepInfo/index.tsx:69 -msgid "Required for this provider" -msgstr "此供應商要求必填" - -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:168 -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:171 -msgid "Resend email" -msgstr "重新傳送郵件" - -#: src/view/com/modals/ChangePassword.tsx:186 -msgid "Reset code" -msgstr "重設碼" - -#: src/view/com/modals/ChangePassword.tsx:193 -msgid "Reset Code" -msgstr "重設碼" - -#: src/view/screens/Settings/index.tsx:900 -#: src/view/screens/Settings/index.tsx:903 -msgid "Reset onboarding state" -msgstr "重設初始設定進行狀態" - -#: src/screens/Login/ForgotPasswordForm.tsx:86 -msgid "Reset password" -msgstr "重設密碼" - -#: src/view/screens/Settings/index.tsx:880 -#: src/view/screens/Settings/index.tsx:883 -msgid "Reset preferences state" -msgstr "重設偏好狀態" - -#: src/view/screens/Settings/index.tsx:901 -msgid "Resets the onboarding state" -msgstr "重設初始設定狀態" - -#: src/view/screens/Settings/index.tsx:881 -msgid "Resets the preferences state" -msgstr "重設偏好狀態" - -#: src/screens/Login/LoginForm.tsx:289 -msgid "Retries login" -msgstr "重試登入" - -#: src/view/com/util/error/ErrorMessage.tsx:57 -#: src/view/com/util/error/ErrorScreen.tsx:74 -msgid "Retries the last action, which errored out" -msgstr "重試上次出錯的操作" - -#: src/components/dms/MessageItem.tsx:241 -#: src/components/Error.tsx:90 -#: src/components/Lists.tsx:104 -#: src/screens/Login/LoginForm.tsx:288 -#: src/screens/Login/LoginForm.tsx:295 -#: src/screens/Messages/Conversation/MessageListError.tsx:25 -#: src/screens/Onboarding/StepInterests/index.tsx:226 -#: src/screens/Onboarding/StepInterests/index.tsx:229 -#: src/screens/Signup/index.tsx:207 -#: src/view/com/util/error/ErrorMessage.tsx:55 -#: src/view/com/util/error/ErrorScreen.tsx:72 -msgid "Retry" -msgstr "重試" - -#: src/components/Error.tsx:98 -#: src/view/screens/ProfileList.tsx:971 -msgid "Return to previous page" -msgstr "返回上一頁" - -#: src/view/screens/NotFound.tsx:59 -msgid "Returns to home page" -msgstr "返回首頁" - -#: src/view/screens/NotFound.tsx:58 -#: src/view/screens/ProfileFeed.tsx:112 -msgid "Returns to previous page" -msgstr "返回上一頁" - -#: src/components/dialogs/BirthDateSettings.tsx:125 -#: src/view/com/composer/GifAltText.tsx:162 -#: src/view/com/composer/GifAltText.tsx:168 -#: src/view/com/modals/ChangeHandle.tsx:168 -#: src/view/com/modals/CreateOrEditList.tsx:326 -#: src/view/com/modals/EditProfile.tsx:225 -msgid "Save" -msgstr "儲存" - -#: src/view/com/lightbox/Lightbox.tsx:133 -#: src/view/com/modals/CreateOrEditList.tsx:334 -msgctxt "action" -msgid "Save" -msgstr "儲存" - -#: src/view/com/modals/AltImage.tsx:132 -msgid "Save alt text" -msgstr "儲存替代文字" - -#: src/components/dialogs/BirthDateSettings.tsx:119 -msgid "Save birthday" -msgstr "儲存生日" - -#: src/view/com/modals/EditProfile.tsx:233 -msgid "Save Changes" -msgstr "儲存更改" - -#: src/view/com/modals/ChangeHandle.tsx:165 -msgid "Save handle change" -msgstr "儲存帳號代碼更改" - -#: src/view/com/modals/crop-image/CropImage.web.tsx:169 -msgid "Save image crop" -msgstr "儲存圖片裁剪" - -#: src/view/screens/ProfileFeed.tsx:331 -#: src/view/screens/ProfileFeed.tsx:337 -msgid "Save to my feeds" -msgstr "儲存到我的動態源" - -#: src/view/screens/SavedFeeds.tsx:145 -msgid "Saved Feeds" -msgstr "已儲存之動態源" - -#: src/view/com/lightbox/Lightbox.tsx:82 -msgid "Saved to your camera roll" -msgstr "儲存至裝置相簿" - -#: src/view/screens/ProfileFeed.tsx:200 -#: src/view/screens/ProfileList.tsx:300 -msgid "Saved to your feeds" -msgstr "儲存到您的動態源" - -#: src/view/com/modals/EditProfile.tsx:226 -msgid "Saves any changes to your profile" -msgstr "儲存個人檔案中所做的變更" - -#: src/view/com/modals/ChangeHandle.tsx:166 -msgid "Saves handle change to {handle}" -msgstr "儲存帳號代碼更改至 {handle}" - -#: src/view/com/modals/crop-image/CropImage.web.tsx:170 -msgid "Saves image crop settings" -msgstr "儲存圖片裁剪設定" - -#: src/components/dms/ChatEmptyPill.tsx:33 -#: src/components/NewskieDialog.tsx:72 -msgid "Say hello!" -msgstr "說句「你好!👋」" - -#: src/screens/Onboarding/index.tsx:33 -msgid "Science" -msgstr "科學" - -#: src/view/screens/ProfileList.tsx:927 -msgid "Scroll to top" -msgstr "滾動到頂部" - -#: src/components/dms/dialogs/SearchablePeopleList.tsx:504 -#: src/Navigation.tsx:494 -#: src/view/com/auth/LoggedOut.tsx:123 -#: src/view/com/modals/ListAddRemoveUsers.tsx:75 -#: src/view/com/util/forms/SearchInput.tsx:67 -#: src/view/com/util/forms/SearchInput.tsx:79 -#: src/view/screens/Search/Search.tsx:421 -#: src/view/screens/Search/Search.tsx:791 -#: src/view/screens/Search/Search.tsx:813 -#: src/view/shell/bottom-bar/BottomBar.tsx:179 -#: src/view/shell/desktop/LeftNav.tsx:343 -#: src/view/shell/desktop/Search.tsx:194 -#: src/view/shell/desktop/Search.tsx:203 -#: src/view/shell/Drawer.tsx:394 -#: src/view/shell/Drawer.tsx:395 -msgid "Search" -msgstr "搜尋" - -#: src/view/shell/desktop/Search.tsx:235 -msgid "Search for \"{query}\"" -msgstr "搜尋「{query}」" - -#: src/view/screens/Search/Search.tsx:869 -msgid "Search for \"{searchText}\"" -msgstr "搜尋「{searchText}」" - -#: src/components/TagMenu/index.tsx:145 -msgid "Search for all posts by @{authorHandle} with tag {displayTag}" -msgstr "搜尋所有由 @{authorHandle} 發佈並具有標籤 {displayTag} 的貼文" - -#: src/components/TagMenu/index.tsx:94 -msgid "Search for all posts with tag {displayTag}" -msgstr "搜尋所有具有標籤 {displayTag} 的貼文" - -#: src/view/com/auth/LoggedOut.tsx:105 -#: src/view/com/auth/LoggedOut.tsx:106 -#: src/view/com/modals/ListAddRemoveUsers.tsx:70 -msgid "Search for users" -msgstr "搜尋用戶" - -#: src/components/dialogs/GifSelect.ios.tsx:159 -#: src/components/dialogs/GifSelect.tsx:169 -msgid "Search GIFs" -msgstr "搜尋 GIF" - -#: src/components/dms/dialogs/SearchablePeopleList.tsx:524 -#: src/components/dms/dialogs/SearchablePeopleList.tsx:525 -msgid "Search profiles" -msgstr "搜尋用戶" - -#: src/components/dialogs/GifSelect.ios.tsx:160 -#: src/components/dialogs/GifSelect.tsx:170 -msgid "Search Tenor" -msgstr "搜尋 Tenor" - -#: src/view/com/modals/ChangeEmail.tsx:105 -msgid "Security Step Required" -msgstr "所需的安全步驟" - -#: src/components/TagMenu/index.web.tsx:66 -msgid "See {truncatedTag} posts" -msgstr "搜尋 {truncatedTag}" - -#: src/components/TagMenu/index.web.tsx:83 -msgid "See {truncatedTag} posts by user" -msgstr "查看該用戶包含 {truncatedTag} 的貼文" - -#: src/components/TagMenu/index.tsx:128 -msgid "See <0>{displayTag} posts" -msgstr "搜尋 <0>{displayTag}" - -#: src/components/TagMenu/index.tsx:187 -msgid "See <0>{displayTag} posts by this user" -msgstr "查看該用戶包含 <0>{displayTag} 的貼文" - -#: src/view/screens/SavedFeeds.tsx:187 -msgid "See this guide" -msgstr "查看指南" - -#: src/view/com/util/Selector.tsx:106 -msgid "Select {item}" -msgstr "選擇 {item}" - -#: src/screens/Onboarding/StepProfile/AvatarCreatorItems.tsx:67 -msgid "Select a color" -msgstr "選擇一個顏色" - -#: src/screens/Login/ChooseAccountForm.tsx:85 -msgid "Select account" -msgstr "選擇帳號" - -#: src/screens/Onboarding/StepProfile/AvatarCircle.tsx:66 -msgid "Select an avatar" -msgstr "選擇一個頭像" - -#: src/screens/Onboarding/StepProfile/AvatarCreatorItems.tsx:65 -msgid "Select an emoji" -msgstr "選擇一個表情符號" - -#: src/screens/Login/index.tsx:120 -msgid "Select from an existing account" -msgstr "從現有帳號中選擇" - -#: src/view/com/composer/photos/SelectGifBtn.tsx:35 -msgid "Select GIF" -msgstr "選擇 GIF" - -#: src/components/dialogs/GifSelect.shared.tsx:29 -msgid "Select GIF \"{0}\"" -msgstr "選擇 GIF「{0}」" - -#: src/view/screens/LanguageSettings.tsx:301 -msgid "Select languages" -msgstr "選擇語言" - -#: src/components/ReportDialog/SelectLabelerView.tsx:30 -msgid "Select moderator" -msgstr "選擇內容管理服務提供者" - -#: src/view/com/util/Selector.tsx:107 -msgid "Select option {i} of {numItems}" -msgstr "選擇 {numItems} 個項目中的第 {i} 項" - -#: src/screens/Onboarding/StepProfile/AvatarCreatorItems.tsx:83 -msgid "Select the {emojiName} emoji as your avatar" -msgstr "選擇 {emojiName} 表情符號作為您的頭像" - -#: src/components/ReportDialog/SubmitView.tsx:135 -msgid "Select the moderation service(s) to report to" -msgstr "選擇要檢舉的內容管理服務提供者" - -#: src/view/com/auth/server-input/index.tsx:82 -msgid "Select the service that hosts your data." -msgstr "選擇用來託管您的資料的服務商。" - -#: src/view/screens/LanguageSettings.tsx:283 -msgid "Select which languages you want your subscribed feeds to include. If none are selected, all languages will be shown." -msgstr "選擇您希望訂閱動態源中所包含的語言。未選擇任何語言時會預設顯示所有語言。" - -#: src/view/screens/LanguageSettings.tsx:99 -msgid "Select your app language for the default text to display in the app." -msgstr "選擇應用程式中的預設語言。" - -#: src/screens/Signup/StepInfo/index.tsx:135 -msgid "Select your date of birth" -msgstr "選擇您的出生日期" - -#: src/screens/Onboarding/StepInterests/index.tsx:201 -msgid "Select your interests from the options below" -msgstr "從下面選擇您感興趣的選項" - -#: src/view/screens/LanguageSettings.tsx:192 -msgid "Select your preferred language for translations in your feed." -msgstr "選擇您在動態中翻譯的偏好目標語言。" - -#: src/components/dms/ChatEmptyPill.tsx:38 -msgid "Send a neat website!" -msgstr "發送一個妙趣的網站!" - -#: src/view/com/modals/VerifyEmail.tsx:210 -#: src/view/com/modals/VerifyEmail.tsx:212 -msgid "Send Confirmation Email" -msgstr "發送確認電子郵件" - -#: src/view/com/modals/DeleteAccount.tsx:149 -msgid "Send email" -msgstr "發送電子郵件" - -#: src/view/com/modals/DeleteAccount.tsx:162 -msgctxt "action" -msgid "Send Email" -msgstr "發送電子郵件" - -#: src/view/shell/Drawer.tsx:329 -#: src/view/shell/Drawer.tsx:350 -msgid "Send feedback" -msgstr "提交意見" - -#: src/screens/Messages/Conversation/MessageInput.tsx:163 -#: src/screens/Messages/Conversation/MessageInput.web.tsx:155 -msgid "Send message" -msgstr "重送訊息" - -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:59 -msgid "Send post to..." -msgstr "傳送貼文給…" - -#: src/components/dms/ReportDialog.tsx:234 -#: src/components/dms/ReportDialog.tsx:237 -#: src/components/ReportDialog/SubmitView.tsx:215 -#: src/components/ReportDialog/SubmitView.tsx:219 -msgid "Send report" -msgstr "提交檢舉" - -#: src/components/ReportDialog/SelectLabelerView.tsx:44 -msgid "Send report to {0}" -msgstr "將檢舉提交至 {0}" - -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:119 -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:122 -msgid "Send verification email" -msgstr "發送驗證電子郵件" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:296 -#: src/view/com/util/forms/PostDropdownBtn.tsx:299 -msgid "Send via direct message" -msgstr "透過私人訊息發送" - -#: src/view/com/modals/DeleteAccount.tsx:151 -msgid "Sends email with confirmation code for account deletion" -msgstr "發送包含帳號刪除確認碼的電子郵件" - -#: src/view/com/auth/server-input/index.tsx:114 -msgid "Server address" -msgstr "伺服器地址" - -#: src/screens/Moderation/index.tsx:304 -msgid "Set birthdate" -msgstr "設定生日" - -#: src/screens/Login/SetNewPasswordForm.tsx:102 -msgid "Set new password" -msgstr "設定新密碼" - -#: src/view/screens/PreferencesFollowingFeed.tsx:224 -msgid "Set this setting to \"No\" to hide all quote posts from your feed. Reposts will still be visible." -msgstr "將此選項設為「關」以隱藏動態中所有引用的貼文,但轉貼依然會顯示。" - -#: src/view/screens/PreferencesFollowingFeed.tsx:121 -msgid "Set this setting to \"No\" to hide all replies from your feed." -msgstr "將此選項設為「關」以隱藏動態中所有回覆貼文。" - -#: src/view/screens/PreferencesFollowingFeed.tsx:190 -msgid "Set this setting to \"No\" to hide all reposts from your feed." -msgstr "將此選項設為「關」以隱藏動態的所有轉貼貼文。" - -#: src/view/screens/PreferencesThreads.tsx:122 -msgid "Set this setting to \"Yes\" to show replies in a threaded view. This is an experimental feature." -msgstr "將此選項設為「開」以單頁顯示樹狀回覆,這是一項實驗性功能。" - -#: src/view/screens/PreferencesFollowingFeed.tsx:260 -msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your Following feed. This is an experimental feature." -msgstr "將此選項設為「是」以在「Following」動態源中顯示您已儲存之動態源中的選錄貼文,這是一項實驗性功能。" - -#: src/screens/Onboarding/Layout.tsx:48 -msgid "Set up your account" -msgstr "設定您的帳號" - -#: src/view/com/modals/ChangeHandle.tsx:261 -msgid "Sets Bluesky username" -msgstr "設定 Bluesky 帳號代碼" - -#: src/view/screens/Settings/index.tsx:461 -msgid "Sets color theme to dark" -msgstr "將色彩主題設定為深色" - -#: src/view/screens/Settings/index.tsx:454 -msgid "Sets color theme to light" -msgstr "將色彩主題設定為亮色" - -#: src/view/screens/Settings/index.tsx:448 -msgid "Sets color theme to system setting" -msgstr "將色彩主題設定為跟隨系統" - -#: src/view/screens/Settings/index.tsx:487 -msgid "Sets dark theme to the dark theme" -msgstr "將深色主題設定為深色" - -#: src/view/screens/Settings/index.tsx:480 -msgid "Sets dark theme to the dim theme" -msgstr "將深色主題設定為昏暗" - -#: src/screens/Login/ForgotPasswordForm.tsx:113 -msgid "Sets email for password reset" -msgstr "設定用於重設密碼的電子郵件" - -#: src/view/com/modals/crop-image/CropImage.web.tsx:146 -msgid "Sets image aspect ratio to square" -msgstr "將圖片比例設定為正方形" - -#: src/view/com/modals/crop-image/CropImage.web.tsx:136 -msgid "Sets image aspect ratio to tall" -msgstr "將圖片比例設定為高" - -#: src/view/com/modals/crop-image/CropImage.web.tsx:126 -msgid "Sets image aspect ratio to wide" -msgstr "將圖片比例設定為寬" - -#: src/Navigation.tsx:145 -#: src/view/screens/Settings/index.tsx:332 -#: src/view/shell/desktop/LeftNav.tsx:389 -#: src/view/shell/Drawer.tsx:559 -#: src/view/shell/Drawer.tsx:560 -msgid "Settings" -msgstr "設定" - -#: src/view/com/modals/SelfLabel.tsx:126 -msgid "Sexual activity or erotic nudity." -msgstr "性行為或性暗示裸露。" - -#: src/lib/moderation/useGlobalLabelStrings.ts:38 -msgid "Sexually Suggestive" -msgstr "性暗示" - -#: src/view/com/lightbox/Lightbox.tsx:142 -msgctxt "action" -msgid "Share" -msgstr "分享" - -#: src/view/com/profile/ProfileMenu.tsx:220 -#: src/view/com/profile/ProfileMenu.tsx:229 -#: src/view/com/util/forms/PostDropdownBtn.tsx:307 -#: src/view/com/util/forms/PostDropdownBtn.tsx:316 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:300 -#: src/view/screens/ProfileList.tsx:428 -msgid "Share" -msgstr "分享" - -#: src/components/dms/ChatEmptyPill.tsx:37 -msgid "Share a cool story!" -msgstr "分享一個有趣的故事!" - -#: src/components/dms/ChatEmptyPill.tsx:36 -msgid "Share a fun fact!" -msgstr "分享一個趣聞!📰" - -#: src/view/com/profile/ProfileMenu.tsx:378 -#: src/view/com/util/forms/PostDropdownBtn.tsx:461 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:316 -msgid "Share anyway" -msgstr "仍然分享" - -#: src/view/screens/ProfileFeed.tsx:357 -#: src/view/screens/ProfileFeed.tsx:359 -msgid "Share feed" -msgstr "分享動態源" - -#: src/view/com/modals/LinkWarning.tsx:89 -#: src/view/com/modals/LinkWarning.tsx:95 -msgid "Share Link" -msgstr "分享連結" - -#: src/components/dms/ChatEmptyPill.tsx:34 -msgid "Share your favorite feed!" -msgstr "分享你喜愛的動態!" - -#: src/view/com/modals/LinkWarning.tsx:92 -msgid "Shares the linked website" -msgstr "分享網站的連結" - -#: src/components/moderation/ContentHider.tsx:116 -#: src/components/moderation/LabelPreference.tsx:136 -#: src/components/moderation/PostHider.tsx:122 -#: src/view/screens/Settings/index.tsx:381 -msgid "Show" -msgstr "顯示" - -#: src/view/com/util/post-embeds/GifEmbed.tsx:169 -msgid "Show alt text" -msgstr "顯示替代文字" - -#: src/components/moderation/ScreenHider.tsx:169 -#: src/components/moderation/ScreenHider.tsx:172 -msgid "Show anyway" -msgstr "仍然顯示" - -#: src/lib/moderation/useLabelBehaviorDescription.ts:27 -#: src/lib/moderation/useLabelBehaviorDescription.ts:63 -msgid "Show badge" -msgstr "顯示標記" - -#: src/lib/moderation/useLabelBehaviorDescription.ts:61 -msgid "Show badge and filter from feeds" -msgstr "顯示標記並從動態源中篩選" - -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:215 -msgid "Show follows similar to {0}" -msgstr "顯示類似於 {0} 的跟隨者" - -#: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:23 -msgid "Show hidden replies" -msgstr "顯示隱藏回覆" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:346 -#: src/view/com/util/forms/PostDropdownBtn.tsx:348 -msgid "Show less like this" -msgstr "減少顯示此類內容" - -#: src/view/com/post-thread/PostThreadItem.tsx:533 -#: src/view/com/post/Post.tsx:227 -#: src/view/com/posts/FeedItem.tsx:396 -msgid "Show More" -msgstr "顯示更多" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:338 -#: src/view/com/util/forms/PostDropdownBtn.tsx:340 -msgid "Show more like this" -msgstr "顯示更多此類內容" - -#: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:23 -msgid "Show muted replies" -msgstr "顯示靜音回覆" - -#: src/view/screens/PreferencesFollowingFeed.tsx:257 -msgid "Show Posts from My Feeds" -msgstr "顯示來自我的動態源之貼文" - -#: src/view/screens/PreferencesFollowingFeed.tsx:221 -msgid "Show Quote Posts" -msgstr "顯示引用貼文" - -#: src/view/screens/PreferencesFollowingFeed.tsx:118 -msgid "Show Replies" -msgstr "顯示回覆" - -#: src/view/screens/PreferencesThreads.tsx:100 -msgid "Show replies by people you follow before all other replies." -msgstr "在所有其他回覆之前顯示您跟隨的人的回覆。" - -#: src/view/screens/PreferencesFollowingFeed.tsx:187 -msgid "Show Reposts" -msgstr "顯示轉貼貼文" - -#: src/components/moderation/ContentHider.tsx:69 -#: src/components/moderation/PostHider.tsx:79 -msgid "Show the content" -msgstr "顯示內容" - -#: src/lib/moderation/useLabelBehaviorDescription.ts:58 -msgid "Show warning" -msgstr "顯示警告" - -#: src/lib/moderation/useLabelBehaviorDescription.ts:56 -msgid "Show warning and filter from feeds" -msgstr "顯示警告並從動態中篩選" - -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:130 -msgid "Shows posts from {0} in your feed" -msgstr "在您的動態中顯示來自 {0} 的貼文" - -#: src/components/dialogs/Signin.tsx:97 -#: src/components/dialogs/Signin.tsx:99 -#: src/screens/Login/index.tsx:100 -#: src/screens/Login/index.tsx:119 -#: src/screens/Login/LoginForm.tsx:154 -#: src/view/com/auth/SplashScreen.tsx:63 -#: src/view/com/auth/SplashScreen.tsx:72 -#: src/view/com/auth/SplashScreen.web.tsx:112 -#: src/view/com/auth/SplashScreen.web.tsx:121 -#: src/view/shell/bottom-bar/BottomBar.tsx:312 -#: src/view/shell/bottom-bar/BottomBar.tsx:313 -#: src/view/shell/bottom-bar/BottomBar.tsx:315 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:204 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:205 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:207 -#: src/view/shell/NavSignupCard.tsx:69 -#: src/view/shell/NavSignupCard.tsx:70 -#: src/view/shell/NavSignupCard.tsx:72 -msgid "Sign in" -msgstr "登入" - -#: src/components/AccountList.tsx:114 -msgid "Sign in as {0}" -msgstr "以 {0} 登入" - -#: src/screens/Login/ChooseAccountForm.tsx:88 -msgid "Sign in as..." -msgstr "登入為…" - -#: src/components/dialogs/Signin.tsx:75 -msgid "Sign in or create your account to join the conversation!" -msgstr "登入或建立您的帳號即可加入對話!" - -#: src/components/dialogs/Signin.tsx:46 -msgid "Sign into Bluesky or create a new account" -msgstr "登入 Bluesky 或建立新帳號" - -#: src/view/screens/Settings/index.tsx:129 -#: src/view/screens/Settings/index.tsx:133 -msgid "Sign out" -msgstr "登出" - -#: src/view/shell/bottom-bar/BottomBar.tsx:302 -#: src/view/shell/bottom-bar/BottomBar.tsx:303 -#: src/view/shell/bottom-bar/BottomBar.tsx:305 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:194 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:195 -#: src/view/shell/bottom-bar/BottomBarWeb.tsx:197 -#: src/view/shell/NavSignupCard.tsx:60 -#: src/view/shell/NavSignupCard.tsx:61 -#: src/view/shell/NavSignupCard.tsx:63 -msgid "Sign up" -msgstr "註冊" - -#: src/view/shell/NavSignupCard.tsx:47 -msgid "Sign up or sign in to join the conversation" -msgstr "註冊或登入即可參與對話" - -#: src/components/moderation/ScreenHider.tsx:97 -#: src/lib/moderation/useGlobalLabelStrings.ts:28 -msgid "Sign-in Required" -msgstr "需要登入" - -#: src/view/screens/Settings/index.tsx:391 -msgid "Signed in as" -msgstr "登入身分" - -#: src/lib/hooks/useAccountSwitcher.ts:44 -#: src/screens/Login/ChooseAccountForm.tsx:60 -msgid "Signed in as @{0}" -msgstr "以 @{0} 身分登入" - -#: src/screens/Onboarding/StepInterests/index.tsx:240 -msgid "Skip" -msgstr "跳過" - -#: src/screens/Onboarding/StepInterests/index.tsx:237 -msgid "Skip this flow" -msgstr "跳過此流程" - -#: src/screens/Onboarding/index.tsx:37 -msgid "Software Dev" -msgstr "軟體開發" - -#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 -#: src/view/com/threadgate/WhoCanReply.tsx:67 -#: src/view/com/threadgate/WhoCanReply.tsx:124 -msgid "Some people can reply" -msgstr "僅部分人可以回覆" - -#: src/screens/Messages/Conversation/index.tsx:106 -msgid "Something went wrong" -msgstr "發生了一些問題" - -#: src/screens/Deactivated.tsx:94 -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:59 -msgid "Something went wrong, please try again" -msgstr "發生了一些問題,請重試" - -#: src/components/ReportDialog/index.tsx:59 -#: src/screens/Moderation/index.tsx:114 -#: src/screens/Profile/Sections/Labels.tsx:87 -msgid "Something went wrong, please try again." -msgstr "發生了一些問題,請重試。" - -#: src/App.native.tsx:92 -#: src/App.web.tsx:74 -msgid "Sorry! Your session expired. Please log in again." -msgstr "抱歉!您的登入會話已過期。請重新登入。" - -#: src/view/screens/PreferencesThreads.tsx:69 -msgid "Sort Replies" -msgstr "排序回覆" - -#: src/view/screens/PreferencesThreads.tsx:72 -msgid "Sort replies to the same post by:" -msgstr "對同一貼文的回覆進行排序:" - -#: src/components/moderation/LabelsOnMeDialog.tsx:168 -msgid "Source: <0>{0}" -msgstr "來源:<0>{0}" - -#: src/lib/moderation/useReportOptions.ts:66 -#: src/lib/moderation/useReportOptions.ts:79 -msgid "Spam" -msgstr "垃圾訊息" - -#: src/lib/moderation/useReportOptions.ts:54 -msgid "Spam; excessive mentions or replies" -msgstr "垃圾訊息、過多的提及或回覆" - -#: src/screens/Onboarding/index.tsx:27 -msgid "Sports" -msgstr "運動" - -#: src/view/com/modals/crop-image/CropImage.web.tsx:145 -msgid "Square" -msgstr "方塊" - -#: src/components/dms/dialogs/NewChatDialog.tsx:61 -msgid "Start a new chat" -msgstr "開始新對話" - -#: src/components/dms/dialogs/SearchablePeopleList.tsx:371 -msgid "Start chat with {displayName}" -msgstr "與 {displayName} 開始對話" - -#: src/components/dms/MessagesNUX.tsx:161 -msgid "Start chatting" -msgstr "開始對話" - -#: src/view/screens/Settings/index.tsx:963 -msgid "Status Page" -msgstr "服務運作狀態頁面" - -#: src/screens/Signup/index.tsx:154 -msgid "Step {0} of {1}" -msgstr "第 {0} 步(共 {1} 步)" - -#: src/view/screens/Settings/index.tsx:304 -msgid "Storage cleared, you need to restart the app now." -msgstr "已清除儲存資料,您需要立即重啟應用程式。" - -#: src/Navigation.tsx:224 -#: src/view/screens/Settings/index.tsx:863 -msgid "Storybook" -msgstr "故事書" - -#: src/components/moderation/LabelsOnMeDialog.tsx:290 -#: src/components/moderation/LabelsOnMeDialog.tsx:291 -#: src/screens/Messages/Conversation/ChatDisabled.tsx:142 -#: src/screens/Messages/Conversation/ChatDisabled.tsx:143 -msgid "Submit" -msgstr "提交" - -#: src/view/screens/ProfileList.tsx:644 -msgid "Subscribe" -msgstr "訂閱" - -#: src/screens/Profile/Sections/Labels.tsx:201 -msgid "Subscribe to @{0} to use these labels:" -msgstr "訂閱 @{0} 以使用這些標記:" - -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:230 -msgid "Subscribe to Labeler" -msgstr "訂閱標記者" - -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:194 -msgid "Subscribe to this labeler" -msgstr "訂閱這個標記者" - -#: src/view/screens/ProfileList.tsx:640 -msgid "Subscribe to this list" -msgstr "訂閱這個列表" - -#: src/view/screens/Search/Explore.tsx:331 -msgid "Suggested accounts" -msgstr "推薦的帳號" - -#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:65 -msgid "Suggested for you" -msgstr "為您推薦" - -#: src/view/com/modals/SelfLabel.tsx:96 -msgid "Suggestive" -msgstr "性暗示" - -#: src/Navigation.tsx:239 -#: src/view/screens/Support.tsx:30 -#: src/view/screens/Support.tsx:33 -msgid "Support" -msgstr "支援" - -#: src/components/dialogs/SwitchAccount.tsx:47 -#: src/components/dialogs/SwitchAccount.tsx:50 -msgid "Switch Account" -msgstr "切換帳號" - -#: src/view/screens/Settings/index.tsx:160 -msgid "Switch to {0}" -msgstr "切換到 {0}" - -#: src/view/screens/Settings/index.tsx:161 -msgid "Switches the account you are logged in to" -msgstr "切換您登入的帳號" - -#: src/view/screens/Settings/index.tsx:445 -msgid "System" -msgstr "系統" - -#: src/view/screens/Settings/index.tsx:851 -msgid "System log" -msgstr "系統日誌" - -#: src/components/dialogs/MutedWords.tsx:323 -msgid "tag" -msgstr "標籤" - -#: src/components/TagMenu/index.tsx:78 -msgid "Tag menu: {displayTag}" -msgstr "標籤選單:{displayTag}" - -#: src/view/com/modals/crop-image/CropImage.web.tsx:135 -msgid "Tall" -msgstr "高" - -#: src/view/com/util/images/AutoSizedImage.tsx:70 -msgid "Tap to view fully" -msgstr "點擊查看完整內容" - -#: src/screens/Onboarding/index.tsx:36 -msgid "Tech" -msgstr "科技" - -#: src/components/dms/ChatEmptyPill.tsx:35 -msgid "Tell a joke!" -msgstr "說個笑話!🤡" - -#: src/view/shell/desktop/RightNav.tsx:86 -msgid "Terms" -msgstr "條款" - -#: src/Navigation.tsx:249 -#: src/screens/Signup/StepInfo/Policies.tsx:49 -#: src/view/screens/Settings/index.tsx:951 -#: src/view/screens/TermsOfService.tsx:29 -#: src/view/shell/Drawer.tsx:279 -msgid "Terms of Service" -msgstr "服務條款" - -#: src/lib/moderation/useReportOptions.ts:59 -#: src/lib/moderation/useReportOptions.ts:93 -#: src/lib/moderation/useReportOptions.ts:101 -msgid "Terms used violate community standards" -msgstr "所使用的文字違反了社群標準" - -#: src/components/dialogs/MutedWords.tsx:323 -msgid "text" -msgstr "文字" - -#: src/components/moderation/LabelsOnMeDialog.tsx:254 -#: src/screens/Messages/Conversation/ChatDisabled.tsx:108 -msgid "Text input field" -msgstr "文字輸入框" - -#: src/components/dms/ReportDialog.tsx:134 -#: src/components/ReportDialog/SubmitView.tsx:77 -msgid "Thank you. Your report has been sent." -msgstr "謝謝,您的檢舉已提交。" - -#: src/view/com/modals/ChangeHandle.tsx:459 -msgid "That contains the following:" -msgstr "其中包含以下內容:" - -#: src/screens/Signup/index.tsx:87 -msgid "That handle is already taken." -msgstr "這個帳號代碼已被使用。" - -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:310 -#: src/view/com/profile/ProfileMenu.tsx:354 -msgid "The account will be able to interact with you after unblocking." -msgstr "解除封鎖後,該帳號將能夠與您互動。" - -#: src/view/screens/CommunityGuidelines.tsx:36 -msgid "The Community Guidelines have been moved to <0/>" -msgstr "社群準則已移動到 <0/>" - -#: src/view/screens/CopyrightPolicy.tsx:33 -msgid "The Copyright Policy has been moved to <0/>" -msgstr "版權政策已移動到 <0/>" - -#: src/view/com/posts/FeedShutdownMsg.tsx:66 -msgid "The feed has been replaced with Discover." -msgstr "此動態源已由「Discover」取代。" - -#: src/components/moderation/LabelsOnMeDialog.tsx:65 -msgid "The following labels were applied to your account." -msgstr "以下標記已套用到您的帳號。" - -#: src/components/moderation/LabelsOnMeDialog.tsx:66 -msgid "The following labels were applied to your content." -msgstr "以下標記已套用到您的內容。" - -#: src/screens/Onboarding/Layout.tsx:58 -msgid "The following steps will help customize your Bluesky experience." -msgstr "以下步驟將幫助自訂您的 Bluesky 體驗。" - -#: src/view/com/post-thread/PostThread.tsx:189 -#: src/view/com/post-thread/PostThread.tsx:201 -msgid "The post may have been deleted." -msgstr "這則貼文可能已被刪除。" - -#: src/view/screens/PrivacyPolicy.tsx:33 -msgid "The Privacy Policy has been moved to <0/>" -msgstr "隱私政策已移動到 <0/>" - -#: src/view/screens/Support.tsx:36 -msgid "The support form has been moved. If you need help, please <0/> or visit {HELP_DESK_URL} to get in touch with us." -msgstr "支援表單已移至別處。如果需協助,請<0/>或前往 {HELP_DESK_URL} 與我們聯繫。" - -#: src/view/screens/TermsOfService.tsx:33 -msgid "The Terms of Service have been moved to" -msgstr "服務條款已遷移到" - -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:86 -msgid "There is no time limit for account deactivation, come back any time." -msgstr "帳號停用沒有時間限制,隨時都可以重新啟用。" - -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:115 -#: src/view/screens/ProfileFeed.tsx:541 -msgid "There was an an issue contacting the server, please check your internet connection and try again." -msgstr "連線至伺服器時出現問題,請檢查您的網路連線並重試。" - -#: src/view/com/posts/FeedErrorMessage.tsx:145 -msgid "There was an an issue removing this feed. Please check your internet connection and try again." -msgstr "刪除動態源時出現問題,請檢查您的網路連線並重試。" - -#: src/view/com/posts/FeedShutdownMsg.tsx:52 -#: src/view/com/posts/FeedShutdownMsg.tsx:70 -#: src/view/screens/ProfileFeed.tsx:205 -msgid "There was an an issue updating your feeds, please check your internet connection and try again." -msgstr "更新動態時出現問題,請檢查您的網路連線並重試。" - -#: src/components/dialogs/GifSelect.ios.tsx:197 -#: src/components/dialogs/GifSelect.tsx:213 -msgid "There was an issue connecting to Tenor." -msgstr "連線到 Tenor 時出現問題。" - -#: src/view/screens/ProfileFeed.tsx:233 -#: src/view/screens/ProfileList.tsx:303 -#: src/view/screens/ProfileList.tsx:322 -#: src/view/screens/SavedFeeds.tsx:237 -#: src/view/screens/SavedFeeds.tsx:263 -#: src/view/screens/SavedFeeds.tsx:289 -msgid "There was an issue contacting the server" -msgstr "連線伺服器時出現問題" - -#: src/view/com/feeds/FeedSourceCard.tsx:128 -#: src/view/com/feeds/FeedSourceCard.tsx:141 -msgid "There was an issue contacting your server" -msgstr "連線伺服器時出現問題" - -#: src/view/com/notifications/Feed.tsx:126 -msgid "There was an issue fetching notifications. Tap here to try again." -msgstr "取得通知時發生問題,點擊這裡重試。" - -#: src/view/com/posts/Feed.tsx:299 -msgid "There was an issue fetching posts. Tap here to try again." -msgstr "取得貼文時發生問題,點擊這裡重試。" - -#: src/view/com/lists/ListMembers.tsx:172 -msgid "There was an issue fetching the list. Tap here to try again." -msgstr "取得列表時發生問題,點擊這裡重試。" - -#: src/view/com/feeds/ProfileFeedgens.tsx:153 -#: src/view/com/lists/ProfileLists.tsx:160 -msgid "There was an issue fetching your lists. Tap here to try again." -msgstr "取得列表時發生問題,點擊這裡重試。" - -#: src/components/dms/ReportDialog.tsx:222 -#: src/components/ReportDialog/SubmitView.tsx:82 -msgid "There was an issue sending your report. Please check your internet connection." -msgstr "提交您的檢舉時出現問題,請檢查您的網路連線。" - -#: src/view/screens/AppPasswords.tsx:70 -msgid "There was an issue with fetching your app passwords" -msgstr "取得應用程式專用密碼時發生問題" - -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:107 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:129 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:143 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:99 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:111 -#: src/view/com/profile/ProfileMenu.tsx:112 -#: src/view/com/profile/ProfileMenu.tsx:123 -#: src/view/com/profile/ProfileMenu.tsx:138 -#: src/view/com/profile/ProfileMenu.tsx:149 -#: src/view/com/profile/ProfileMenu.tsx:163 -#: src/view/com/profile/ProfileMenu.tsx:176 -msgid "There was an issue! {0}" -msgstr "發生問題!{0}" - -#: src/view/screens/ProfileList.tsx:335 -#: src/view/screens/ProfileList.tsx:349 -#: src/view/screens/ProfileList.tsx:363 -#: src/view/screens/ProfileList.tsx:377 -msgid "There was an issue. Please check your internet connection and try again." -msgstr "發生問題了。請檢查您的網路連線並重試。" - -#: src/components/dialogs/GifSelect.ios.tsx:239 -#: src/components/dialogs/GifSelect.tsx:257 -#: src/view/com/util/ErrorBoundary.tsx:57 -msgid "There was an unexpected issue in the application. Please let us know if this happened to you!" -msgstr "應用程式中發生了意外問題。請告訴我們是否發生在您身上!" - -#: src/screens/SignupQueued.tsx:112 -msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." -msgstr "Bluesky 迎來了大量新用戶!我們將儘快啟用您的帳號。" - -#: src/components/moderation/ScreenHider.tsx:116 -msgid "This {screenDescription} has been flagged:" -msgstr "{screenDescription} 已被標記:" - -#: src/components/moderation/ScreenHider.tsx:111 -msgid "This account has requested that users sign in to view their profile." -msgstr "此帳號要求使用者登入後才能查看其個人檔案。" - -#: src/components/dms/BlockedByListDialog.tsx:34 -msgid "This account is blocked by one or more of your moderation lists. To unblock, please visit the lists directly and remove this user." -msgstr "此帳號已被一個或多個內容管理清單封鎖。若要解除封鎖,請直接瀏覽這些清單並刪除此使用者。" - -#: src/components/moderation/LabelsOnMeDialog.tsx:239 -msgid "This appeal will be sent to <0>{0}." -msgstr "此申訴將被提交至 <0>{0}。" - -#: src/screens/Messages/Conversation/ChatDisabled.tsx:104 -msgid "This appeal will be sent to Bluesky's moderation service." -msgstr "此申訴將發送至 Bluesky 的內容管理服務。" - -#: src/screens/Messages/Conversation/MessageListError.tsx:18 -msgid "This chat was disconnected" -msgstr "對話已中斷連線" - -#: src/lib/moderation/useGlobalLabelStrings.ts:19 -msgid "This content has been hidden by the moderators." -msgstr "此內容已被內容管理者隱藏。" - -#: src/lib/moderation/useGlobalLabelStrings.ts:24 -msgid "This content has received a general warning from moderators." -msgstr "此內容已套用內容管理提供者所標記的普通警告。" - -#: src/components/dialogs/EmbedConsent.tsx:64 -msgid "This content is hosted by {0}. Do you want to enable external media?" -msgstr "此內容由 {0} 託管。是否要啟用外部媒體?" - -#: src/components/moderation/ModerationDetailsDialog.tsx:77 -#: src/lib/moderation/useModerationCauseDescription.ts:79 -msgid "This content is not available because one of the users involved has blocked the other." -msgstr "由於其中一個用戶封鎖了另一個用戶,無法查看此內容。" - -#: src/view/com/posts/FeedErrorMessage.tsx:114 -msgid "This content is not viewable without a Bluesky account." -msgstr "沒有 Bluesky 帳號,無法查看此內容。" - -#: src/screens/Messages/List/ChatListItem.tsx:213 -msgid "This conversation is with a deleted or a deactivated account. Press for options." -msgstr "此對話是與已刪除或停用的帳號進行的。點擊以查看選項。" - -#: src/view/screens/Settings/ExportCarDialog.tsx:93 -msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost." -msgstr "此功能目前為測試版本。您可以在<0>這篇部落格文章中瞭解更多有關資訊。" - -#: src/view/com/posts/FeedErrorMessage.tsx:120 -msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." -msgstr "此動態源由於目前使用人數眾多而暫時無法使用。請稍後再試。" - -#: src/view/com/posts/CustomFeedEmptyState.tsx:37 -msgid "This feed is empty! You may need to follow more users or tune your language settings." -msgstr "這個動態源是空的!您或許需要先跟隨更多的人或檢查您的語言設定。" - -#: src/view/screens/ProfileFeed.tsx:471 -#: src/view/screens/ProfileList.tsx:729 -msgid "This feed is empty." -msgstr "這裡是空的。" - -#: src/view/com/posts/FeedShutdownMsg.tsx:97 -msgid "This feed is no longer online. We are showing <0>Discover instead." -msgstr "此動態源已經下線。我們將展示「<0>Discover」動態源。" - -#: src/components/dialogs/BirthDateSettings.tsx:41 -msgid "This information is not shared with other users." -msgstr "此資訊不會分享給其他用戶。" - -#: src/view/com/modals/VerifyEmail.tsx:127 -msgid "This is important in case you ever need to change your email or reset your password." -msgstr "這很重要,以防您將來需要更改電子郵件地址或重設密碼。" - -#: src/components/moderation/ModerationDetailsDialog.tsx:127 -msgid "This label was applied by <0>{0}." -msgstr "此標記由 <0>{0} 新增。" - -#: src/components/moderation/ModerationDetailsDialog.tsx:125 -msgid "This label was applied by the author." -msgstr "此標記由發布者新增。" - -#: src/components/moderation/LabelsOnMeDialog.tsx:166 -msgid "This label was applied by you." -msgstr "此標記由您新增。" - -#: src/screens/Profile/Sections/Labels.tsx:188 -msgid "This labeler hasn't declared what labels it publishes, and may not be active." -msgstr "此標記者尚未宣告它發佈的標記,而且可能不會生效。" - -#: src/view/com/modals/LinkWarning.tsx:72 -msgid "This link is taking you to the following website:" -msgstr "此連結將帶您到以下網站:" - -#: src/view/screens/ProfileList.tsx:907 -msgid "This list is empty!" -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 "此內容管理服務暫時無法使用,詳情請見下文。如果問題持續存在,請與我們聯絡。" - -#: src/view/com/modals/AddAppPasswords.tsx:110 -msgid "This name is already in use" -msgstr "此名稱已被使用" - -#: src/view/com/post-thread/PostThreadItem.tsx:135 -msgid "This post has been deleted." -msgstr "這則貼文已被刪除。" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:458 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:313 -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:440 -msgid "This post will be hidden from feeds." -msgstr "這則貼文將從動態隱藏。" - -#: src/view/com/profile/ProfileMenu.tsx:375 -msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't logged in." -msgstr "只有登入用戶能見到此個人檔案。 未登入的人將看不到它。" - -#: src/screens/Signup/StepInfo/Policies.tsx:37 -msgid "This service has not provided terms of service or a privacy policy." -msgstr "此服務尚未提供服務條款或隱私政策。" - -#: src/view/com/modals/ChangeHandle.tsx:439 -msgid "This should create a domain record at:" -msgstr "這應該會在以下位置建立一個域名記錄:" - -#: src/view/com/profile/ProfileFollowers.tsx:87 -msgid "This user doesn't have any followers." -msgstr "此用戶沒有任何追隨者。" - -#: src/components/dms/MessagesListBlockedFooter.tsx:60 -msgid "This user has blocked you" -msgstr "這個用戶已封鎖您" - -#: src/components/moderation/ModerationDetailsDialog.tsx:72 -#: src/lib/moderation/useModerationCauseDescription.ts:70 -msgid "This user has blocked you. You cannot view their content." -msgstr "此用戶已封鎖您,您無法查看他們的內容。" - -#: src/lib/moderation/useGlobalLabelStrings.ts:30 -msgid "This user has requested that their content only be shown to signed-in users." -msgstr "此用戶要求僅將其內容顯示給已登入的用戶。" - -#: src/components/moderation/ModerationDetailsDialog.tsx:55 -msgid "This user is included in the <0>{0} list which you have blocked." -msgstr "此用戶包含在您已封鎖的 <0>{0} 列表中。" - -#: src/components/moderation/ModerationDetailsDialog.tsx:84 -msgid "This user is included in the <0>{0} list which you have muted." -msgstr "此用戶包含在您已靜音的 <0>{0} 列表中。" - -#: src/components/NewskieDialog.tsx:50 -msgid "This user is new here. Press for more info about when they joined." -msgstr "該用戶是新來帳號,請按此了解更多有關他們何時加入的資訊。" - -#: src/view/com/profile/ProfileFollows.tsx:87 -msgid "This user isn't following anyone." -msgstr "此用戶未跟隨任何人。" - -#: src/components/dialogs/MutedWords.tsx:283 -msgid "This will delete {0} from your muted words. You can always add it back later." -msgstr "這將從您的靜音文字中刪除 {0},您隨時可以新增回來。" - -#: src/view/screens/Settings/index.tsx:594 -msgid "Thread preferences" -msgstr "討論串偏好" - -#: src/view/screens/PreferencesThreads.tsx:53 -#: src/view/screens/Settings/index.tsx:604 -msgid "Thread Preferences" -msgstr "討論串偏好" - -#: src/view/screens/PreferencesThreads.tsx:119 -msgid "Threaded Mode" -msgstr "樹狀顯示模式" - -#: src/Navigation.tsx:282 -msgid "Threads Preferences" -msgstr "討論串偏好" - -#: src/view/screens/Settings/DisableEmail2FADialog.tsx:102 -msgid "To disable the email 2FA method, please verify your access to the email address." -msgstr "若要關閉電子郵件雙重驗證,請驗證您的電子郵件地址。" - -#: src/components/dms/ReportConversationPrompt.tsx:20 -msgid "To report a conversation, please report one of its messages via the conversation screen. This lets our moderators understand the context of your issue." -msgstr "若要檢舉對話,請透過對話畫面檢舉其中一則訊息。這可以讓我們的內容管理者瞭解問題的來龍去脈。" - -#: src/components/ReportDialog/SelectLabelerView.tsx:33 -msgid "To whom would you like to send this report?" -msgstr "您希望向誰提交此檢舉?" - -#: src/components/dialogs/MutedWords.tsx:112 -msgid "Toggle between muted word options." -msgstr "在靜音文字選項之間切換。" - -#: src/view/com/util/forms/DropdownButton.tsx:255 -msgid "Toggle dropdown" -msgstr "切換下拉式選單" - -#: src/screens/Moderation/index.tsx:332 -msgid "Toggle to enable or disable adult content" -msgstr "切換以啟用或停用成人內容" - -#: src/screens/Hashtag.tsx:88 -#: src/view/screens/Search/Search.tsx:349 -msgid "Top" -msgstr "熱門" - -#: src/view/com/modals/EditImage.tsx:272 -msgid "Transformations" -msgstr "轉換" - -#: src/components/dms/MessageMenu.tsx:103 -#: src/components/dms/MessageMenu.tsx:105 -#: src/view/com/post-thread/PostThreadItem.tsx:681 -#: src/view/com/post-thread/PostThreadItem.tsx:683 -#: src/view/com/util/forms/PostDropdownBtn.tsx:277 -#: src/view/com/util/forms/PostDropdownBtn.tsx:279 -msgid "Translate" -msgstr "翻譯" - -#: src/view/com/util/error/ErrorScreen.tsx:82 -msgctxt "action" -msgid "Try again" -msgstr "重試" - -#: src/view/screens/Settings/index.tsx:745 -msgid "Two-factor authentication" -msgstr "雙重驗證" - -#: src/screens/Messages/Conversation/MessageInput.tsx:139 -msgid "Type your message here" -msgstr "在此輸入訊息" - -#: src/view/com/modals/ChangeHandle.tsx:422 -msgid "Type:" -msgstr "類型:" - -#: src/view/screens/ProfileList.tsx:535 -msgid "Un-block list" -msgstr "取消封鎖列表" - -#: src/view/screens/ProfileList.tsx:520 -msgid "Un-mute list" -msgstr "取消靜音列表" - -#: src/screens/Login/ForgotPasswordForm.tsx:74 -#: src/screens/Login/index.tsx:78 -#: src/screens/Login/LoginForm.tsx:142 -#: src/screens/Login/SetNewPasswordForm.tsx:77 -#: src/screens/Signup/index.tsx:66 -#: src/view/com/modals/ChangePassword.tsx:71 -msgid "Unable to contact your service. Please check your Internet connection." -msgstr "無法連線到服務,請檢查您的網路連線。" - -#: src/components/dms/MessagesListBlockedFooter.tsx:89 -#: src/components/dms/MessagesListBlockedFooter.tsx:96 -#: src/components/dms/MessagesListBlockedFooter.tsx:104 -#: src/components/dms/MessagesListBlockedFooter.tsx:111 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:192 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:314 -#: src/view/com/profile/ProfileMenu.tsx:366 -#: src/view/screens/ProfileList.tsx:626 -msgid "Unblock" -msgstr "解除封鎖" - -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:197 -msgctxt "action" -msgid "Unblock" -msgstr "解除封鎖" - -#: src/components/dms/ConvoMenu.tsx:188 -#: src/components/dms/ConvoMenu.tsx:192 -msgid "Unblock account" -msgstr "解除封鎖帳號" - -#: src/view/com/profile/ProfileMenu.tsx:304 -#: src/view/com/profile/ProfileMenu.tsx:310 -msgid "Unblock Account" -msgstr "解除封鎖帳號" - -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:308 -#: src/view/com/profile/ProfileMenu.tsx:348 -msgid "Unblock Account?" -msgstr "解除封鎖?" - -#: src/view/com/util/post-ctrls/RepostButton.tsx:64 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:70 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:74 -msgid "Undo repost" -msgstr "取消轉貼" - -#: src/view/com/profile/FollowButton.tsx:60 -msgctxt "action" -msgid "Unfollow" -msgstr "取消跟隨" - -#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:247 -msgid "Unfollow" -msgstr "取消跟隨" - -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:237 -msgid "Unfollow {0}" -msgstr "取消跟隨 {0}" - -#: src/view/com/profile/ProfileMenu.tsx:246 -#: src/view/com/profile/ProfileMenu.tsx:256 -msgid "Unfollow Account" -msgstr "取消跟隨" - -#: src/view/screens/ProfileFeed.tsx:570 -msgid "Unlike this feed" -msgstr "取消喜歡這個動態源" - -#: src/components/TagMenu/index.tsx:249 -#: src/view/screens/ProfileList.tsx:633 -msgid "Unmute" -msgstr "取消靜音" - -#: src/components/TagMenu/index.web.tsx:104 -msgid "Unmute {truncatedTag}" -msgstr "取消靜音 {truncatedTag}" - -#: src/view/com/profile/ProfileMenu.tsx:283 -#: src/view/com/profile/ProfileMenu.tsx:289 -msgid "Unmute Account" -msgstr "取消靜音帳號" - -#: src/components/TagMenu/index.tsx:208 -msgid "Unmute all {displayTag} posts" -msgstr "取消對所有 {displayTag} 貼文的靜音" - -#: src/components/dms/ConvoMenu.tsx:176 -msgid "Unmute conversation" -msgstr "取消靜音對話" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:362 -#: src/view/com/util/forms/PostDropdownBtn.tsx:367 -msgid "Unmute thread" -msgstr "取消靜音討論串" - -#: src/view/screens/ProfileFeed.tsx:290 -#: src/view/screens/ProfileList.tsx:617 -msgid "Unpin" -msgstr "取消釘選" - -#: src/view/screens/ProfileFeed.tsx:287 -msgid "Unpin from home" -msgstr "自首頁取消釘選" - -#: src/view/screens/ProfileList.tsx:500 -msgid "Unpin moderation list" -msgstr "取消釘選內容管理列表" - -#: src/view/screens/ProfileList.tsx:290 -msgid "Unpinned from your feeds" -msgstr "已從您的動態源取消釘選" - -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:228 -msgid "Unsubscribe" -msgstr "取消訂閱" - -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:193 -msgid "Unsubscribe from this labeler" -msgstr "取消訂閱這個標記者" - -#: src/lib/moderation/useReportOptions.ts:71 -#: src/lib/moderation/useReportOptions.ts:84 -msgid "Unwanted Sexual Content" -msgstr "不受歡迎的色情內容" - -#: src/view/com/modals/UserAddRemoveLists.tsx:83 -msgid "Update {displayName} in Lists" -msgstr "更新列表中的 {displayName}" - -#: src/view/com/modals/ChangeHandle.tsx:502 -msgid "Update to {handle}" -msgstr "更新至 {handle}" - -#: src/screens/Login/SetNewPasswordForm.tsx:186 -msgid "Updating..." -msgstr "更新中…" - -#: src/screens/Onboarding/StepProfile/index.tsx:281 -msgid "Upload a photo instead" -msgstr "或是上傳圖片" - -#: src/view/com/modals/ChangeHandle.tsx:448 -msgid "Upload a text file to:" -msgstr "上傳文字檔案至:" - -#: src/view/com/util/UserAvatar.tsx:352 -#: src/view/com/util/UserAvatar.tsx:355 -#: src/view/com/util/UserBanner.tsx:123 -#: src/view/com/util/UserBanner.tsx:126 -msgid "Upload from Camera" -msgstr "從相機上傳" - -#: src/view/com/util/UserAvatar.tsx:369 -#: src/view/com/util/UserBanner.tsx:140 -msgid "Upload from Files" -msgstr "從檔案上傳" - -#: src/view/com/util/UserAvatar.tsx:363 -#: src/view/com/util/UserAvatar.tsx:367 -#: src/view/com/util/UserBanner.tsx:134 -#: src/view/com/util/UserBanner.tsx:138 -msgid "Upload from Library" -msgstr "從圖片庫上傳" - -#: src/view/com/modals/ChangeHandle.tsx:402 -msgid "Use a file on your server" -msgstr "使用您伺服器上的檔案" - -#: src/view/screens/AppPasswords.tsx:200 -msgid "Use app passwords to login to other Bluesky clients without giving full access to your account or password." -msgstr "使用應用程式專用密碼登入到其他 Bluesky 客戶端,而無需提供完整的帳號權限和密碼。" - -#: src/view/com/modals/ChangeHandle.tsx:513 -msgid "Use bsky.social as hosting provider" -msgstr "使用 bsky.social 作為託管服務供應商" - -#: src/view/com/modals/ChangeHandle.tsx:512 -msgid "Use default provider" -msgstr "使用預設託管服務供應商" - -#: src/view/com/modals/InAppBrowserConsent.tsx:56 -#: src/view/com/modals/InAppBrowserConsent.tsx:58 -msgid "Use in-app browser" -msgstr "使用內建瀏覽器" - -#: src/view/com/modals/InAppBrowserConsent.tsx:66 -#: src/view/com/modals/InAppBrowserConsent.tsx:68 -msgid "Use my default browser" -msgstr "使用我的預設瀏覽器" - -#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:53 -msgid "Use recommended" -msgstr "使用推薦" - -#: src/view/com/modals/ChangeHandle.tsx:394 -msgid "Use the DNS panel" -msgstr "使用 DNS 控制台" - -#: src/view/com/modals/AddAppPasswords.tsx:205 -msgid "Use this to sign into the other app along with your handle." -msgstr "使用這個和您的帳號代碼一起登入其他應用程式。" - -#: src/view/com/modals/InviteCodes.tsx:201 -msgid "Used by:" -msgstr "使用者:" - -#: src/components/moderation/ModerationDetailsDialog.tsx:64 -#: src/lib/moderation/useModerationCauseDescription.ts:58 -msgid "User Blocked" -msgstr "用戶被封鎖" - -#: src/lib/moderation/useModerationCauseDescription.ts:50 -msgid "User Blocked by \"{0}\"" -msgstr "用戶被「{0}」封鎖" - -#: src/components/dms/BlockedByListDialog.tsx:27 -msgid "User blocked by list" -msgstr "用戶已被列表封鎖" - -#: src/components/moderation/ModerationDetailsDialog.tsx:53 -msgid "User Blocked by List" -msgstr "用戶被列表封鎖" - -#: src/lib/moderation/useModerationCauseDescription.ts:68 -msgid "User Blocking You" -msgstr "用戶封鎖了您" - -#: src/components/moderation/ModerationDetailsDialog.tsx:70 -msgid "User Blocks You" -msgstr "用戶封鎖了您" - -#: src/view/com/lists/ListCard.tsx:87 -#: src/view/com/modals/UserAddRemoveLists.tsx:209 -msgid "User list by {0}" -msgstr "{0} 的用戶列表" - -#: src/view/screens/ProfileList.tsx:831 -msgid "User list by <0/>" -msgstr "<0/> 的用戶列表" - -#: src/view/com/lists/ListCard.tsx:85 -#: src/view/com/modals/UserAddRemoveLists.tsx:207 -#: src/view/screens/ProfileList.tsx:829 -msgid "User list by you" -msgstr "您的用戶列表" - -#: src/view/com/modals/CreateOrEditList.tsx:184 -msgid "User list created" -msgstr "已建立用戶列表" - -#: src/view/com/modals/CreateOrEditList.tsx:170 -msgid "User list updated" -msgstr "已更新用戶列表" - -#: src/view/screens/Lists.tsx:63 -msgid "User Lists" -msgstr "用戶列表" - -#: src/screens/Login/LoginForm.tsx:174 -msgid "Username or email address" -msgstr "帳號代碼或電子郵件地址" - -#: src/view/screens/ProfileList.tsx:865 -msgid "Users" -msgstr "用戶" - -#: src/view/com/threadgate/WhoCanReply.tsx:274 -msgid "users followed by <0/>" -msgstr "被 <0/> 跟隨的用戶" - -#: src/components/dms/MessagesNUX.tsx:140 -#: src/components/dms/MessagesNUX.tsx:143 -#: src/screens/Messages/Settings.tsx:84 -#: src/screens/Messages/Settings.tsx:87 -msgid "Users I follow" -msgstr "我跟隨的用戶" - -#: src/view/com/modals/Threadgate.tsx:109 -msgid "Users in \"{0}\"" -msgstr "「{0}」中的用戶" - -#: src/components/LikesDialog.tsx:85 -msgid "Users that have liked this content or profile" -msgstr "喜歡此內容或個人檔案的用戶" - -#: src/view/com/modals/ChangeHandle.tsx:430 -msgid "Value:" -msgstr "值:" - -#: src/view/com/modals/ChangeHandle.tsx:504 -msgid "Verify DNS Record" -msgstr "驗證 DNS 紀錄" - -#: src/view/screens/Settings/index.tsx:982 -msgid "Verify email" -msgstr "驗證電子郵件" - -#: src/view/screens/Settings/index.tsx:1007 -msgid "Verify my email" -msgstr "驗證我的電子郵件" - -#: src/view/screens/Settings/index.tsx:1016 -msgid "Verify My Email" -msgstr "驗證我的電子郵件" - -#: src/view/com/modals/ChangeEmail.tsx:200 -#: src/view/com/modals/ChangeEmail.tsx:202 -msgid "Verify New Email" -msgstr "驗證新的電子郵件" - -#: src/view/com/modals/ChangeHandle.tsx:505 -msgid "Verify Text File" -msgstr "驗證文字檔案" - -#: src/view/com/modals/VerifyEmail.tsx:111 -msgid "Verify Your Email" -msgstr "驗證您的電子郵件" - -#: src/view/screens/Settings/index.tsx:935 -msgid "Version {appVersion} {bundleInfo}" -msgstr "版本 {appVersion} {bundleInfo}" - -#: src/screens/Onboarding/index.tsx:39 -msgid "Video Games" -msgstr "電子遊戲" - -#: src/screens/Profile/Header/Shell.tsx:113 -msgid "View {0}'s avatar" -msgstr "查看 {0} 的頭像" - -#: src/view/com/notifications/FeedItem.tsx:215 -msgid "View {0}'s profile" -msgstr "查看 {0} 的個人檔案" - -#: src/components/ProfileHoverCard/index.web.tsx:430 -msgid "View blocked user's profile" -msgstr "查看已封鎖用戶的個人檔案" - -#: src/view/screens/Log.tsx:56 -msgid "View debug entry" -msgstr "查看偵錯項目" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:136 -msgid "View details" -msgstr "查看詳細資訊" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:131 -msgid "View details for reporting a copyright violation" -msgstr "查看詳細資訊以檢舉侵犯版權" - -#: src/view/com/posts/FeedSlice.tsx:124 -msgid "View full thread" -msgstr "查看整個討論串" - -#: src/components/moderation/LabelsOnMe.tsx:48 -msgid "View information about these labels" -msgstr "查看有關這些標記的資訊" - -#: src/components/ProfileHoverCard/index.web.tsx:418 -#: src/components/ProfileHoverCard/index.web.tsx:436 -#: src/components/ProfileHoverCard/index.web.tsx:463 -#: src/view/com/posts/AviFollowButton.tsx:58 -#: src/view/com/posts/FeedErrorMessage.tsx:174 -msgid "View profile" -msgstr "查看資料" - -#: src/view/com/profile/ProfileSubpageHeader.tsx:127 -msgid "View the avatar" -msgstr "查看頭像" - -#: src/components/LabelingServiceCard/index.tsx:137 -msgid "View the labeling service provided by @{0}" -msgstr "查看由 @{0} 提供的標記服務" - -#: src/view/screens/ProfileFeed.tsx:582 -msgid "View users who like this feed" -msgstr "查看喜歡此動態源的用戶" - -#: src/view/com/home/HomeHeaderLayout.web.tsx:79 -#: src/view/com/home/HomeHeaderLayoutMobile.tsx:84 -msgid "View your feeds and explore more" -msgstr "查看您的動態並探索更多內容" - -#: src/view/com/modals/LinkWarning.tsx:89 -#: src/view/com/modals/LinkWarning.tsx:95 -msgid "Visit Site" -msgstr "造訪網站" - -#: src/components/moderation/LabelPreference.tsx:135 -#: src/lib/moderation/useLabelBehaviorDescription.ts:17 -#: src/lib/moderation/useLabelBehaviorDescription.ts:22 -msgid "Warn" -msgstr "警告" - -#: src/lib/moderation/useLabelBehaviorDescription.ts:48 -msgid "Warn content" -msgstr "警告內容" - -#: src/lib/moderation/useLabelBehaviorDescription.ts:46 -msgid "Warn content and filter from feeds" -msgstr "警告內容並從動態源中過濾" - -#: src/screens/Hashtag.tsx:210 -msgid "We couldn't find any results for that hashtag." -msgstr "我們找不到任何與該標籤相關的結果。" - -#: src/screens/Messages/Conversation/index.tsx:107 -msgid "We couldn't load this conversation" -msgstr "我們無法載入這個對話" - -#: src/screens/SignupQueued.tsx:139 -msgid "We estimate {estimatedTime} until your account is ready." -msgstr "我們估計還需要 {estimatedTime} 才能準備好您的帳號。" - -#: src/screens/Onboarding/StepFinished.tsx:126 -msgid "We hope you have a wonderful time. Remember, Bluesky is:" -msgstr "我們希望您在此度過愉快的時光。請記住,Bluesky 是:" - -#: src/view/com/posts/DiscoverFallbackHeader.tsx:29 -msgid "We ran out of posts from your follows. Here's the latest from <0/>." -msgstr "您已看完了您跟隨的貼文。這是來自 <0/> 的最新貼文。" - -#: src/components/dialogs/MutedWords.tsx:203 -msgid "We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." -msgstr "我們建議避免新增在許多貼文中常用的文字,因為這可能令您看不到任何貼文。" - -#: src/components/dialogs/BirthDateSettings.tsx:52 -msgid "We were unable to load your birth date preferences. Please try again." -msgstr "我們無法載入您的出生日期偏好,請再試一次。" - -#: src/screens/Moderation/index.tsx:385 -msgid "We were unable to load your configured labelers at this time." -msgstr "我們目前無法載入您已設定的標記者。" - -#: src/screens/Onboarding/StepInterests/index.tsx:138 -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." -msgstr "我們無法連線到網際網路,請重試以繼續設定您的帳號。如果仍繼續失敗,您可以選擇跳過此流程。" - -#: src/screens/SignupQueued.tsx:143 -msgid "We will let you know when your account is ready." -msgstr "我們會在您的帳號準備好時通知您。" - -#: src/screens/Onboarding/StepInterests/index.tsx:143 -msgid "We'll use this to help customize your experience." -msgstr "我們將使用這些資訊來協助訂製您的體驗。" - -#: src/components/dms/dialogs/SearchablePeopleList.tsx:90 -msgid "We're having network issues, try again" -msgstr "我們遇到網路問題,請重試" - -#: src/screens/Signup/index.tsx:142 -msgid "We're so excited to have you join us!" -msgstr "我們非常高興您加入我們!" - -#: src/view/screens/ProfileList.tsx:91 -msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." -msgstr "很抱歉,我們無法解析此列表。如果問題持續發生,請聯繫列表建立者 @{handleOrDid}。" - -#: src/components/dialogs/MutedWords.tsx:229 -msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." -msgstr "很抱歉,我們目前無法載入您的靜音文字。請稍後再試。" - -#: src/view/screens/Search/Search.tsx:206 -msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." -msgstr "很抱歉,無法完成您的搜尋請求。請稍後再試。" - -#: src/view/com/composer/Composer.tsx:318 -msgid "We're sorry! The post you are replying to has been deleted." -msgstr "很抱歉!您回覆的貼文已被刪除。" - -#: src/components/Lists.tsx:212 -#: src/view/screens/NotFound.tsx:48 -msgid "We're sorry! We can't find the page you were looking for." -msgstr "很抱歉!我們找不到您正在尋找的頁面。" - -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:330 -msgid "We're sorry! You can only subscribe to twenty labelers, and you've reached your limit of twenty." -msgstr "抱歉!您只能訂閱二十個標記者,您已達到二十個的限制。" - -#: src/screens/Deactivated.tsx:128 -msgid "Welcome back!" -msgstr "歡迎回來!" - -#: src/screens/Onboarding/StepInterests/index.tsx:135 -msgid "What are your interests?" -msgstr "您感興趣的是什麼?" - -#: src/view/com/auth/SplashScreen.tsx:40 -#: src/view/com/auth/SplashScreen.web.tsx:86 -#: src/view/com/composer/Composer.tsx:359 -msgid "What's up?" -msgstr "發生了什麼新鮮事?" - -#: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:78 -msgid "Which languages are used in this post?" -msgstr "這個貼文使用了哪些語言?" - -#: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:77 -msgid "Which languages would you like to see in your algorithmic feeds?" -msgstr "您想在演算法動態源中看到哪些語言?" - -#: src/components/dms/MessagesNUX.tsx:110 -#: src/components/dms/MessagesNUX.tsx:124 -msgid "Who can message you?" -msgstr "誰可以傳送訊息給您?" - -#: src/view/com/modals/Threadgate.tsx:69 -#: src/view/com/threadgate/WhoCanReply.tsx:73 -#: src/view/com/threadgate/WhoCanReply.tsx:130 -msgid "Who can reply" -msgstr "誰可以回覆" - -#: src/view/com/threadgate/WhoCanReply.tsx:206 -msgid "Who can reply dialog" -msgstr "「誰可以回覆」對話窗" - -#: src/view/com/threadgate/WhoCanReply.tsx:210 -msgid "Who can reply?" -msgstr "誰可以回覆?" - -#: src/screens/Home/NoFeedsPinned.tsx:79 -#: src/screens/Messages/List/index.tsx:185 -msgid "Whoops!" -msgstr "哎呀!" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:44 -msgid "Why should this content be reviewed?" -msgstr "為什麼應該審查這個內容?" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:57 -msgid "Why should this feed be reviewed?" -msgstr "為什麼應該審查這個動態源?" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:54 -msgid "Why should this list be reviewed?" -msgstr "為什麼應該審查這個列表?" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:60 -msgid "Why should this message be reviewed?" -msgstr "為什麼應該審查這則訊息?" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:51 -msgid "Why should this post be reviewed?" -msgstr "為什麼應該審查這則貼文?" - -#: src/components/ReportDialog/SelectReportOptionView.tsx:48 -msgid "Why should this user be reviewed?" -msgstr "為什麼應該審查這個用戶?" - -#: src/view/com/modals/crop-image/CropImage.web.tsx:125 -msgid "Wide" -msgstr "寬" - -#: src/screens/Messages/Conversation/MessageInput.tsx:140 -#: src/screens/Messages/Conversation/MessageInput.web.tsx:134 -msgid "Write a message" -msgstr "撰寫訊息" - -#: src/view/com/composer/Composer.tsx:551 -msgid "Write post" -msgstr "撰寫貼文" - -#: src/view/com/composer/Composer.tsx:358 -#: src/view/com/composer/Prompt.tsx:39 -msgid "Write your reply" -msgstr "撰寫您的回覆" - -#: src/screens/Onboarding/index.tsx:25 -msgid "Writers" -msgstr "作家" - -#: src/view/com/composer/select-language/SuggestedLanguage.tsx:77 -#: src/view/screens/PreferencesFollowingFeed.tsx:128 -#: src/view/screens/PreferencesFollowingFeed.tsx:200 -#: src/view/screens/PreferencesFollowingFeed.tsx:235 -#: src/view/screens/PreferencesFollowingFeed.tsx:270 -#: src/view/screens/PreferencesThreads.tsx:106 -#: src/view/screens/PreferencesThreads.tsx:129 -msgid "Yes" -msgstr "開" - -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:106 -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:108 -msgid "Yes, deactivate" -msgstr "確定並停用" - -#: src/screens/Deactivated.tsx:150 -msgid "Yes, reactivate my account" -msgstr "確定並停用我的帳號" - -#: src/components/dms/MessageItem.tsx:188 -msgid "Yesterday, {time}" -msgstr "昨天,{time}" - -#: src/screens/SignupQueued.tsx:136 -msgid "You are in line." -msgstr "你正處於隊列之中。" - -#: src/view/com/profile/ProfileFollows.tsx:86 -msgid "You are not following anyone." -msgstr "您沒有跟隨任何人。" - -#: src/view/com/posts/FollowingEmptyState.tsx:63 -#: src/view/com/posts/FollowingEndOfFeed.tsx:64 -msgid "You can also discover new Custom Feeds to follow." -msgstr "您也可以探索並跟隨新的自訂動態源。" - -#: src/view/com/modals/DeleteAccount.tsx:202 -msgid "You can also temporarily deactivate your account instead, and reactivate it at any time." -msgstr "您也可以暫時停用帳號,然後隨時重新啟用。" - -#: src/components/dms/MessagesNUX.tsx:119 -msgid "You can change this at any time." -msgstr "您可以隨時變更該設定。" - -#: src/screens/Messages/Settings.tsx:111 -msgid "You can continue ongoing conversations regardless of which setting you choose." -msgstr "無論選擇哪種設定,都不會影響已發起的對話。" - -#: src/screens/Login/index.tsx:158 -#: src/screens/Login/PasswordUpdatedForm.tsx:33 -msgid "You can now sign in with your new password." -msgstr "您現在可以使用新密碼登入。" - -#: src/screens/Deactivated.tsx:136 -msgid "You can reactivate your account to continue logging in. Your profile and posts will be visible to other users." -msgstr "您可以登入以重新啟用帳號。其他用戶將可以重新看到您的個人檔案和貼文。" - -#: src/view/com/profile/ProfileFollowers.tsx:86 -msgid "You do not have any followers." -msgstr "您沒有任何跟隨者。" - -#: src/screens/Profile/KnownFollowers.tsx:99 -msgid "You don't follow any users who follow @{name}." -msgstr "您沒有跟隨任何也跟隨 @{name} 之用戶。" - -#: 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." -msgstr "您目前還沒有邀請碼!當您持續使用 Bluesky 一段時間後,我們將提供一些新的邀請碼給您。" - -#: src/view/screens/SavedFeeds.tsx:117 -msgid "You don't have any pinned feeds." -msgstr "您目前還沒有任何釘選的動態源。" - -#: src/view/screens/SavedFeeds.tsx:158 -msgid "You don't have any saved feeds." -msgstr "您目前還沒有任何已儲存的動態源。" - -#: src/view/com/post-thread/PostThread.tsx:195 -msgid "You have blocked the author or you have been blocked by the author." -msgstr "您已封鎖該作者,或您已被該作者封鎖。" - -#: src/components/dms/MessagesListBlockedFooter.tsx:58 -msgid "You have blocked this user" -msgstr "您已封鎖該用戶" - -#: src/components/moderation/ModerationDetailsDialog.tsx:66 -#: src/lib/moderation/useModerationCauseDescription.ts:52 -#: src/lib/moderation/useModerationCauseDescription.ts:60 -msgid "You have blocked this user. You cannot view their content." -msgstr "您已封鎖了此用戶,您將無法查看他們發佈的內容。" - -#: src/screens/Login/SetNewPasswordForm.tsx:54 -#: src/screens/Login/SetNewPasswordForm.tsx:91 -#: src/view/com/modals/ChangePassword.tsx:88 -#: src/view/com/modals/ChangePassword.tsx:122 -msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." -msgstr "您輸入的邀請碼無效。它應該長得像這樣 XXXXX-XXXXX。" - -#: src/lib/moderation/useModerationCauseDescription.ts:111 -msgid "You have hidden this post" -msgstr "您已隱藏這則貼文" - -#: src/components/moderation/ModerationDetailsDialog.tsx:101 -msgid "You have hidden this post." -msgstr "您已隱藏這則貼文。" - -#: src/components/moderation/ModerationDetailsDialog.tsx:94 -#: src/lib/moderation/useModerationCauseDescription.ts:94 -msgid "You have muted this account." -msgstr "您已隱藏這個帳號。" - -#: src/lib/moderation/useModerationCauseDescription.ts:88 -msgid "You have muted this user" -msgstr "您已靜音這個用戶" - -#: src/screens/Messages/List/index.tsx:225 -msgid "You have no conversations yet. Start one!" -msgstr "您還沒有對話,與其他用戶開始對話吧!" - -#: src/view/com/feeds/ProfileFeedgens.tsx:141 -msgid "You have no feeds." -msgstr "您沒有建立任何動態源。" - -#: src/view/com/lists/MyLists.tsx:90 -#: src/view/com/lists/ProfileLists.tsx:145 -msgid "You have no lists." -msgstr "您沒有建立任何列表。" - -#: src/view/screens/ModerationBlockedAccounts.tsx:134 -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/AppPasswords.tsx:91 -msgid "You have not created any app passwords yet. You can create one by pressing the button below." -msgstr "您還沒有建立任何應用程式專用密碼,如您想建立一個,按下面的按鈕。" - -#: src/view/screens/ModerationMutedAccounts.tsx:133 -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/components/Lists.tsx:52 -msgid "You have reached the end" -msgstr "已經到底部啦!" - -#: src/components/dialogs/MutedWords.tsx:249 -msgid "You haven't muted any words or tags yet" -msgstr "您還沒有隱藏任何文字或標籤" - -#: src/components/moderation/LabelsOnMeDialog.tsx:86 -msgid "You may appeal non-self labels if you feel they were placed in error." -msgstr "如果您認為這些標記有誤,且標記並非由您新增,您可以提出申訴。" - -#: src/components/moderation/LabelsOnMeDialog.tsx:91 -msgid "You may appeal these labels if you feel they were placed in error." -msgstr "如果您覺得這些標記有誤,您可以提出申訴。" - -#: src/screens/Signup/StepInfo/Policies.tsx:79 -msgid "You must be 13 years of age or older to sign up." -msgstr "您必須年滿 13 歲才能註冊。" - -#: src/components/ReportDialog/SubmitView.tsx:205 -msgid "You must select at least one labeler for a report" -msgstr "您必須選擇至少一個標記者來提交檢舉" - -#: src/screens/Deactivated.tsx:131 -msgid "You previously deactivated @{0}." -msgstr "您之前停用了 @{0}。" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:174 -msgid "You will no longer receive notifications for this thread" -msgstr "您將不再收到這條討論串的通知" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:170 -msgid "You will now receive notifications for this thread" -msgstr "您將收到這條討論串的通知" - -#: src/screens/Login/SetNewPasswordForm.tsx:104 -msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password." -msgstr "您將收到一封包含重設碼的電子郵件。請在此輸入該「重設碼」,然後輸入您的新密碼。" - -#: src/screens/Messages/List/ChatListItem.tsx:114 -msgid "You: {0}" -msgstr "您:{0}" - -#: src/screens/Messages/List/ChatListItem.tsx:143 -msgid "You: {defaultEmbeddedContentMessage}" -msgstr "您:{defaultEmbeddedContentMessage}" - -#: src/screens/Messages/List/ChatListItem.tsx:136 -msgid "You: {short}" -msgstr "您:{short}" - -#: src/screens/SignupQueued.tsx:93 -#: src/screens/SignupQueued.tsx:94 -#: src/screens/SignupQueued.tsx:109 -msgid "You're in line" -msgstr "輪到您了" - -#: src/screens/Deactivated.tsx:89 -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:54 -msgid "You're logged in with an App Password. Please log in with your main password to continue deactivating your account." -msgstr "您正在使用應用程式專用密碼登入。請使用您的主密碼登入,以繼續停用您的帳號。" - -#: src/screens/Onboarding/StepFinished.tsx:123 -msgid "You're ready to go!" -msgstr "您已完成設定!" - -#: src/components/moderation/ModerationDetailsDialog.tsx:98 -#: src/lib/moderation/useModerationCauseDescription.ts:103 -msgid "You've chosen to hide a word or tag within this post." -msgstr "您選擇在這則貼文中隱藏文字或標籤。" - -#: src/view/com/posts/FollowingEndOfFeed.tsx:44 -msgid "You've reached the end of your feed! Find some more accounts to follow." -msgstr "您已經瀏覽完貼文啦!跟隨其他帳號吧。" - -#: src/screens/Signup/index.tsx:164 -msgid "Your account" -msgstr "您的帳號" - -#: src/view/com/modals/DeleteAccount.tsx:88 -msgid "Your account has been deleted" -msgstr "您的帳號已刪除" - -#: src/view/screens/Settings/ExportCarDialog.tsx:65 -msgid "Your account repository, containing all public data records, can be downloaded as a \"CAR\" file. This file does not include media embeds, such as images, or your private data, which must be fetched separately." -msgstr "您可以將您的帳號存放庫下載為一個「CAR」檔案。該檔案包含了所有公開的資料紀錄,但不包括嵌入媒體,例如圖片或您的私人資料,目前這些資料必須另外擷取。" - -#: src/screens/Signup/StepInfo/index.tsx:123 -msgid "Your birth date" -msgstr "您的生日" - -#: src/screens/Messages/Conversation/ChatDisabled.tsx:25 -msgid "Your chats have been disabled" -msgstr "您的對話功能已被停用" - -#: src/view/com/modals/InAppBrowserConsent.tsx:47 -msgid "Your choice will be saved, but can be changed later in settings." -msgstr "您的選擇將被儲存,但可以稍後在設定中更改。" - -#: src/screens/Login/ForgotPasswordForm.tsx:57 -#: src/screens/Signup/state.ts:220 -#: src/view/com/modals/ChangePassword.tsx:55 -msgid "Your email appears to be invalid." -msgstr "您的電子郵件地址似乎無效。" - -#: src/view/com/modals/ChangeEmail.tsx:120 -msgid "Your email has been updated but not verified. As a next step, please verify your new email." -msgstr "您的電子郵件地址已更新但尚未驗證。作為下一步,請驗證您的新電子郵件地址。" - -#: src/view/com/modals/VerifyEmail.tsx:122 -msgid "Your email has not yet been verified. This is an important security step which we recommend." -msgstr "您的電子郵件地址尚未驗證。這是一個我們建議的重要安全步驟。" - -#: src/view/com/posts/FollowingEmptyState.tsx:43 -msgid "Your following feed is empty! Follow more users to see what's happening." -msgstr "您的「Following」動態源是空的!跟隨更多用戶來看看發生了什麼事情。" - -#: src/screens/Signup/StepHandle.tsx:73 -msgid "Your full handle will be" -msgstr "您的完整帳號代碼將修改為" - -#: src/view/com/modals/ChangeHandle.tsx:265 -msgid "Your full handle will be <0>@{0}" -msgstr "您的完整帳號代碼將修改為 <0>@{0}" - -#: src/components/dialogs/MutedWords.tsx:220 -msgid "Your muted words" -msgstr "您的靜音文字" - -#: src/view/com/modals/ChangePassword.tsx:158 -msgid "Your password has been changed successfully!" -msgstr "您的密碼已成功更改!" - -#: src/view/com/composer/Composer.tsx:349 -msgid "Your post has been published" -msgstr "您的貼文已發佈" - -#: src/screens/Onboarding/StepFinished.tsx:138 -msgid "Your posts, likes, and blocks are public. Mutes are private." -msgstr "您的貼文、按喜歡和封鎖是公開可見的,而靜音是私人的。" - -#: src/view/screens/Settings/index.tsx:148 -msgid "Your profile" -msgstr "您的個人檔案" - -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:75 -msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in." -msgstr "其他 Bluesky 用戶將無法再看到您的個人檔案、貼文、動態和列表。您可以隨時登入以重新啟用您的帳號。" - -#: src/view/com/composer/Composer.tsx:348 -msgid "Your reply has been published" -msgstr "您的回覆已發佈" - -#: src/components/dms/ReportDialog.tsx:162 -msgid "Your report will be sent to the Bluesky Moderation Service" -msgstr "您的檢舉將發送至 Bluesky 內容管理服務" - -#: src/screens/Signup/index.tsx:166 -msgid "Your user handle" -msgstr "您的帳號代碼" +msgid "" +msgstr "" +"Project-Id-Version: zh-TW for bluesky-social-app\n" +"POT-Creation-Date: \n" +"Report-Msgid-Bugs-To: Kuwa Lee , Frudrax Cheng \n" +"PO-Revision-Date: 2024-06-20 23:03+0800\n" +"Last-Translator: \n" +"Language-Team: Frudrax Cheng , Kuwa Lee , noeFly, snowleo208, Kisaragi Hiu, Yi-Jyun Pan, toto6038, cirx1e\n" +"Language: zh_TW\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: @lingui/cli\n" +"Plural-Forms: \n" + +#: src/screens/Messages/List/ChatListItem.tsx:120 +msgid "(contains embedded content)" +msgstr "(含有嵌入內容)" + +#: src/view/com/modals/VerifyEmail.tsx:150 +msgid "(no email)" +msgstr "(沒有電子郵件)" + +#: src/view/com/notifications/FeedItem.tsx:263 +msgid "{0, plural, one {{formattedCount} other} other {{formattedCount} others}}" +msgstr "{0, plural, one {其他 {formattedCount} 個人} other {其他 {formattedCount} 個人}}" + +#: src/components/moderation/LabelsOnMe.tsx:55 +msgid "{0, plural, one {# label has been placed on this account} other {# labels have been placed on this account}}" +msgstr "{0, plural, one {該帳號有 # 個標記} other {該帳號有 # 個標記}}" + +#: src/components/moderation/LabelsOnMe.tsx:61 +msgid "{0, plural, one {# label has been placed on this content} other {# labels have been placed on this content}}" +msgstr "{0, plural, one {該內容有 # 個標記} other {該內容有 # 個標記}}" + +#: src/view/com/util/post-ctrls/RepostButton.tsx:66 +msgid "{0, plural, one {# repost} other {# reposts}}" +msgstr "{0, plural, one {# 個轉貼} other {# 個轉貼}}" + +#: src/components/ProfileHoverCard/index.web.tsx:398 +#: src/screens/Profile/Header/Metrics.tsx:23 +msgid "{0, plural, one {follower} other {followers}}" +msgstr "{0, plural, one {個跟隨者} other {個跟隨者}}" + +#: src/components/ProfileHoverCard/index.web.tsx:402 +#: src/screens/Profile/Header/Metrics.tsx:27 +msgid "{0, plural, one {following} other {following}}" +msgstr "{0, plural, one {個跟隨中} other {個跟隨中}}" + +#: src/view/com/util/post-ctrls/PostCtrls.tsx:255 +msgid "{0, plural, one {Like (# like)} other {Like (# likes)}}" +msgstr "{0, plural, one {喜歡(# 個喜歡)} other {喜歡(# 個喜歡)}}" + +#: src/view/com/post-thread/PostThreadItem.tsx:382 +msgid "{0, plural, one {like} other {likes}}" +msgstr "{0, plural, one {喜歡} other {喜歡}}" + +#: src/components/FeedCard.tsx:111 +#: src/view/com/feeds/FeedSourceCard.tsx:301 +msgid "{0, plural, one {Liked by # user} other {Liked by # users}}" +msgstr "{0, plural,one {# 個用戶已喜歡} other {# 個用戶已喜歡}}" + +#: src/screens/Profile/Header/Metrics.tsx:59 +msgid "{0, plural, one {post} other {posts}}" +msgstr "{0, plural, one {則貼文} other {則貼文}}" + +#: src/view/com/util/post-ctrls/PostCtrls.tsx:213 +msgid "{0, plural, one {Reply (# reply)} other {Reply (# replies)}}" +msgstr "{0, plural, one {回覆(# 個回覆)} other {回覆(# 個回覆)}}" + +#: src/view/com/post-thread/PostThreadItem.tsx:362 +msgid "{0, plural, one {repost} other {reposts}}" +msgstr "{0, plural, one {轉貼} other {轉貼}}" + +#: src/view/com/util/post-ctrls/PostCtrls.tsx:251 +msgid "{0, plural, one {Unlike (# like)} other {Unlike (# likes)}}" +msgstr "{0, plural, one {撤回喜歡(# 個喜歡)} other {撤回喜歡(# 個喜歡)}}" + +#: src/view/com/util/UserAvatar.tsx:419 +msgid "{0}'s avatar" +msgstr "{0} 的頭像" + +#: src/components/LabelingServiceCard/index.tsx:71 +msgid "{count, plural, one {Liked by # user} other {Liked by # users}}" +msgstr "{count, plural, one {# 個用戶已喜歡} other {# 個用戶已喜歡}}" + +#: src/lib/hooks/useTimeAgo.ts:69 +msgid "{diff, plural, one {day} other {days}}" +msgstr "{diff, plural, one {天} other {天}}" + +#: src/lib/hooks/useTimeAgo.ts:64 +msgid "{diff, plural, one {hour} other {hours}}" +msgstr "{diff, plural, one {時} other {時}}" + +#: src/lib/hooks/useTimeAgo.ts:59 +msgid "{diff, plural, one {minute} other {minutes}}" +msgstr "{diff, plural, one {分} other {分}}" + +#: src/lib/hooks/useTimeAgo.ts:75 +msgid "{diff, plural, one {month} other {months}}" +msgstr "{diff, plural, one {月} other {月}}" + +#: src/lib/hooks/useTimeAgo.ts:54 +msgid "{diffSeconds, plural, one {second} other {seconds}}" +msgstr "{diffSeconds, plural, one {秒} other {秒}}" + +#: src/screens/SignupQueued.tsx:207 +msgid "{estimatedTimeHrs, plural, one {hour} other {hours}}" +msgstr "{estimatedTimeHrs, plural, one {時} other {時}}" + +#: src/screens/SignupQueued.tsx:213 +msgid "{estimatedTimeMins, plural, one {minute} other {minutes}}" +msgstr "{estimatedTimeMins, plural, one {分} other {分}}" + +#: src/components/ProfileHoverCard/index.web.tsx:503 +#: src/screens/Profile/Header/Metrics.tsx:50 +msgid "{following} following" +msgstr "{following} 個跟隨中" + +#: src/components/dms/dialogs/SearchablePeopleList.tsx:405 +msgid "{handle} can't be messaged" +msgstr "無法傳送訊息給 {handle}" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:286 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:299 +#: src/view/screens/ProfileFeed.tsx:585 +msgid "{likeCount, plural, one {Liked by # user} other {Liked by # users}}" +msgstr "{likeCount, plural, one {# 個用戶已喜歡} other {# 個用戶已喜歡}}" + +#: src/view/shell/Drawer.tsx:462 +msgid "{numUnreadNotifications} unread" +msgstr "{numUnreadNotifications} 個未讀通知" + +#: src/components/NewskieDialog.tsx:75 +msgid "{profileName} joined Bluesky {0} ago" +msgstr "{profileName} 在 {0} 前加入了 Bluesky" + +#: src/view/screens/PreferencesFollowingFeed.tsx:67 +msgid "{value, plural, =0 {Show all replies} one {Show replies with at least # like} other {Show replies with at least # likes}}" +msgstr "{value, plural, =0 {顯示所有回覆} one {顯示至少 # 個喜歡的回覆} other {顯示至少 # 個喜歡的回覆}}" + +#: src/view/com/threadgate/WhoCanReply.tsx:290 +msgid "<0/> members" +msgstr "<0/> 個成員" + +#: src/view/shell/Drawer.tsx:101 +msgid "<0>{0} {1, plural, one {follower} other {followers}}" +msgstr "<0>{0} {1, plural, one {個跟隨者} other {個跟隨者}}" + +#: src/view/shell/Drawer.tsx:112 +msgid "<0>{0} {1, plural, one {following} other {following}}" +msgstr "<0>{0} {1, plural, one {個跟隨中} other {個跟隨中}}" + +#: src/view/com/modals/SelfLabel.tsx:135 +msgid "<0>Not Applicable. This warning is only available for posts with media attached." +msgstr "<0>不適用。 此警告只適用於附帶媒體的貼文。" + +#: src/screens/Profile/Header/Handle.tsx:50 +msgid "⚠Invalid Handle" +msgstr "⚠無效的帳號代碼" + +#: src/screens/Login/LoginForm.tsx:244 +msgid "2FA Confirmation" +msgstr "雙重驗證" + +#: src/view/com/util/ViewHeader.tsx:93 +#: src/view/screens/Search/Search.tsx:684 +msgid "Access navigation links and settings" +msgstr "存取導覽連結和設定" + +#: src/view/com/home/HomeHeaderLayoutMobile.tsx:56 +msgid "Access profile and other navigation links" +msgstr "存取個人檔案和其他導覽連結" + +#: src/view/com/modals/EditImage.tsx:300 +#: src/view/screens/Settings/index.tsx:518 +msgid "Accessibility" +msgstr "無障礙" + +#: src/view/screens/Settings/index.tsx:509 +msgid "Accessibility settings" +msgstr "無障礙設定" + +#: src/Navigation.tsx:296 +#: src/view/screens/AccessibilitySettings.tsx:69 +msgid "Accessibility Settings" +msgstr "無障礙設定" + +#: src/screens/Login/LoginForm.tsx:167 +#: src/view/screens/Settings/index.tsx:345 +#: src/view/screens/Settings/index.tsx:752 +msgid "Account" +msgstr "帳號" + +#: src/view/com/profile/ProfileMenu.tsx:145 +msgid "Account blocked" +msgstr "已封鎖帳號" + +#: src/view/com/profile/ProfileMenu.tsx:159 +msgid "Account followed" +msgstr "已跟隨帳號" + +#: src/view/com/profile/ProfileMenu.tsx:119 +msgid "Account muted" +msgstr "已靜音帳號" + +#: src/components/moderation/ModerationDetailsDialog.tsx:93 +#: src/lib/moderation/useModerationCauseDescription.ts:93 +msgid "Account Muted" +msgstr "已靜音帳號" + +#: src/components/moderation/ModerationDetailsDialog.tsx:82 +msgid "Account Muted by List" +msgstr "帳號已被列表靜音" + +#: src/view/com/util/AccountDropdownBtn.tsx:41 +msgid "Account options" +msgstr "帳號選項" + +#: src/view/com/util/AccountDropdownBtn.tsx:25 +msgid "Account removed from quick access" +msgstr "已從快速存取中移除帳號" + +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:139 +#: src/view/com/profile/ProfileMenu.tsx:134 +msgid "Account unblocked" +msgstr "已解除封鎖帳號" + +#: src/view/com/profile/ProfileMenu.tsx:172 +msgid "Account unfollowed" +msgstr "已取消跟隨帳號" + +#: src/view/com/profile/ProfileMenu.tsx:108 +msgid "Account unmuted" +msgstr "已取消靜音帳號" + +#: src/components/dialogs/MutedWords.tsx:164 +#: src/view/com/modals/ListAddRemoveUsers.tsx:268 +#: src/view/com/modals/UserAddRemoveLists.tsx:230 +#: src/view/screens/ProfileList.tsx:881 +msgid "Add" +msgstr "新增" + +#: src/view/com/modals/SelfLabel.tsx:57 +msgid "Add a content warning" +msgstr "新增內容警告" + +#: src/view/screens/ProfileList.tsx:871 +msgid "Add a user to this list" +msgstr "將用戶新增至此列表" + +#: src/components/dialogs/SwitchAccount.tsx:56 +#: src/screens/Deactivated.tsx:199 +#: src/view/screens/Settings/index.tsx:422 +#: src/view/screens/Settings/index.tsx:431 +msgid "Add account" +msgstr "新增帳號" + +#: src/view/com/composer/GifAltText.tsx:69 +#: src/view/com/composer/GifAltText.tsx:135 +#: src/view/com/composer/GifAltText.tsx:175 +#: src/view/com/composer/photos/Gallery.tsx:120 +#: src/view/com/composer/photos/Gallery.tsx:187 +#: src/view/com/modals/AltImage.tsx:118 +msgid "Add alt text" +msgstr "新增替代文字" + +#: src/view/screens/AppPasswords.tsx:106 +#: src/view/screens/AppPasswords.tsx:148 +#: src/view/screens/AppPasswords.tsx:161 +msgid "Add App Password" +msgstr "新增應用程式專用密碼" + +#: src/components/dialogs/MutedWords.tsx:157 +msgid "Add mute word for configured settings" +msgstr "在已配置的設定中新增靜音文字" + +#: src/components/dialogs/MutedWords.tsx:86 +msgid "Add muted words and tags" +msgstr "新增靜音文字及標籤" + +#: src/screens/Home/NoFeedsPinned.tsx:99 +msgid "Add recommended feeds" +msgstr "新增推薦的動態源" + +#: src/screens/Feeds/NoFollowingFeed.tsx:41 +msgid "Add the default feed of only people you follow" +msgstr "新增預設的「Following」動態源,它只會顯示您跟隨的人" + +#: src/view/com/modals/ChangeHandle.tsx:410 +msgid "Add the following DNS record to your domain:" +msgstr "將以下 DNS 記錄新增到您的網域:" + +#: src/components/FeedCard.tsx:180 +msgid "Add this feed to your feeds" +msgstr "將此新增至您的動態源" + +#: src/view/com/profile/ProfileMenu.tsx:268 +#: src/view/com/profile/ProfileMenu.tsx:271 +msgid "Add to Lists" +msgstr "新增至列表" + +#: src/view/com/feeds/FeedSourceCard.tsx:267 +msgid "Add to my feeds" +msgstr "加入到我的動態源" + +#: src/view/com/modals/ListAddRemoveUsers.tsx:191 +#: src/view/com/modals/UserAddRemoveLists.tsx:157 +msgid "Added to list" +msgstr "新增至列表" + +#: src/view/com/feeds/FeedSourceCard.tsx:126 +msgid "Added to my feeds" +msgstr "加入到我的動態源" + +#: src/view/screens/PreferencesFollowingFeed.tsx:172 +msgid "Adjust the number of likes a reply must have to be shown in your feed." +msgstr "調整回覆貼文在您的動態中顯示所需的最低喜歡數量。" + +#: src/lib/moderation/useGlobalLabelStrings.ts:34 +#: src/view/com/modals/SelfLabel.tsx:76 +msgid "Adult Content" +msgstr "成人內容" + +#: src/components/moderation/LabelPreference.tsx:242 +msgid "Adult content is disabled." +msgstr "成人內容已停用。" + +#: src/screens/Moderation/index.tsx:375 +#: src/view/screens/Settings/index.tsx:686 +msgid "Advanced" +msgstr "進階設定" + +#: src/view/screens/Feeds.tsx:737 +msgid "All the feeds you've saved, right in one place." +msgstr "以下是您儲存的動態源。" + +#: src/view/com/modals/AddAppPasswords.tsx:187 +#: src/view/com/modals/AddAppPasswords.tsx:194 +msgid "Allow access to your direct messages" +msgstr "允許存取您的私人訊息" + +#: src/screens/Messages/Settings.tsx:62 +#: src/screens/Messages/Settings.tsx:65 +msgid "Allow new messages from" +msgstr "允許這些人向您發起對話:" + +#: src/screens/Login/ForgotPasswordForm.tsx:178 +#: src/view/com/modals/ChangePassword.tsx:171 +msgid "Already have a code?" +msgstr "已經有重置碼了?" + +#: src/screens/Login/ChooseAccountForm.tsx:49 +msgid "Already signed in as @{0}" +msgstr "已以 @{0} 身份登入" + +#: src/view/com/composer/GifAltText.tsx:93 +#: src/view/com/composer/photos/Gallery.tsx:144 +#: src/view/com/util/post-embeds/GifEmbed.tsx:177 +msgid "ALT" +msgstr "ALT" + +#: src/view/com/composer/GifAltText.tsx:144 +#: src/view/com/modals/EditImage.tsx:316 +#: src/view/screens/AccessibilitySettings.tsx:83 +msgid "Alt text" +msgstr "替代文字" + +#: src/view/com/util/post-embeds/GifEmbed.tsx:183 +msgid "Alt Text" +msgstr "替代文字" + +#: src/view/com/composer/photos/Gallery.tsx:224 +msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone." +msgstr "替代文字為盲人和視障人士描述圖片及提供情境。" + +#: src/view/com/modals/VerifyEmail.tsx:132 +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:96 +msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below." +msgstr "一封電子郵件已發送至 {0}。請查閱郵件並在下方輸入驗證碼。" + +#: src/view/com/modals/ChangeEmail.tsx:114 +msgid "An email has been sent to your previous address, {0}. It includes a confirmation code which you can enter below." +msgstr "一封電子郵件已發送至先前填寫的電子郵件地址 {0}。請查閱郵件並在下方輸入驗證碼。" + +#: src/components/dialogs/GifSelect.tsx:252 +msgid "An error occured" +msgstr "發生錯誤" + +#: src/lib/moderation/useReportOptions.ts:27 +msgid "An issue not included in these options" +msgstr "問題不在上述選項" + +#: src/components/hooks/useFollowMethods.ts:35 +#: src/components/hooks/useFollowMethods.ts:50 +#: src/view/com/profile/FollowButton.tsx:35 +#: src/view/com/profile/FollowButton.tsx:45 +#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:188 +#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:198 +msgid "An issue occurred, please try again." +msgstr "出現問題,請再試一次。" + +#: src/screens/Onboarding/StepInterests/index.tsx:194 +msgid "an unknown error occurred" +msgstr "出現未知錯誤" + +#: src/view/com/notifications/FeedItem.tsx:260 +#: src/view/com/threadgate/WhoCanReply.tsx:311 +msgid "and" +msgstr "和" + +#: src/screens/Onboarding/index.tsx:29 +msgid "Animals" +msgstr "動物" + +#: src/view/com/util/post-embeds/GifEmbed.tsx:149 +msgid "Animated GIF" +msgstr "GIF 動畫" + +#: src/lib/moderation/useReportOptions.ts:32 +msgid "Anti-Social Behavior" +msgstr "反社會行為" + +#: src/view/screens/LanguageSettings.tsx:96 +msgid "App Language" +msgstr "應用程式語言" + +#: src/view/screens/AppPasswords.tsx:228 +msgid "App password deleted" +msgstr "應用程式專用密碼已刪除" + +#: src/view/com/modals/AddAppPasswords.tsx:138 +msgid "App Password names can only contain letters, numbers, spaces, dashes, and underscores." +msgstr "應用程式專用密碼只能包含字母、數字、空格、破折號及底線。" + +#: src/view/com/modals/AddAppPasswords.tsx:103 +msgid "App Password names must be at least 4 characters long." +msgstr "應用程式專用密碼名稱必須至少為 4 個字元。" + +#: src/view/screens/Settings/index.tsx:697 +msgid "App password settings" +msgstr "應用程式專用密碼設定" + +#: src/Navigation.tsx:264 +#: src/view/screens/AppPasswords.tsx:192 +#: src/view/screens/Settings/index.tsx:706 +msgid "App Passwords" +msgstr "應用程式專用密碼" + +#: src/components/moderation/LabelsOnMeDialog.tsx:151 +#: src/components/moderation/LabelsOnMeDialog.tsx:154 +msgid "Appeal" +msgstr "申訴" + +#: src/components/moderation/LabelsOnMeDialog.tsx:236 +msgid "Appeal \"{0}\" label" +msgstr "申訴「{0}」標記" + +#: src/components/moderation/LabelsOnMeDialog.tsx:227 +#: src/screens/Messages/Conversation/ChatDisabled.tsx:91 +msgid "Appeal submitted" +msgstr "已提交申訴" + +#: src/screens/Messages/Conversation/ChatDisabled.tsx:51 +#: src/screens/Messages/Conversation/ChatDisabled.tsx:53 +#: src/screens/Messages/Conversation/ChatDisabled.tsx:99 +#: src/screens/Messages/Conversation/ChatDisabled.tsx:101 +msgid "Appeal this decision" +msgstr "對此決定提出上訴" + +#: src/view/screens/Settings/index.tsx:439 +msgid "Appearance" +msgstr "外觀" + +#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:47 +#: src/screens/Home/NoFeedsPinned.tsx:93 +msgid "Apply default recommended feeds" +msgstr "使用預設推薦的動態源" + +#: src/view/screens/AppPasswords.tsx:282 +msgid "Are you sure you want to delete the app password \"{name}\"?" +msgstr "您確定要刪除這個應用程式專用密碼「{name}」嗎?" + +#: src/components/dms/MessageMenu.tsx:149 +msgid "Are you sure you want to delete this message? The message will be deleted for you, but not for the other participant." +msgstr "您確定要刪除這則訊息嗎?該訊息將為您刪除,但不會為其他參與者刪除。" + +#: src/components/dms/LeaveConvoPrompt.tsx:48 +msgid "Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for the other participant." +msgstr "您確定要離開此對話嗎?您的訊息將為您刪除,但不會為其他參與者刪除。" + +#: src/view/com/feeds/FeedSourceCard.tsx:314 +msgid "Are you sure you want to remove {0} from your feeds?" +msgstr "您確定要從您的動態中移除 {0} 嗎?" + +#: src/components/FeedCard.tsx:197 +msgid "Are you sure you want to remove this from your feeds?" +msgstr "您確定要將此從您的動態源中移除嗎?" + +#: src/view/com/composer/Composer.tsx:632 +msgid "Are you sure you'd like to discard this draft?" +msgstr "您確定要捨棄此草稿嗎?" + +#: src/components/dialogs/MutedWords.tsx:281 +msgid "Are you sure?" +msgstr "您確定嗎?" + +#: src/view/com/composer/select-language/SuggestedLanguage.tsx:60 +msgid "Are you writing in <0>{0}?" +msgstr "您正在使用 <0>{0} 書寫嗎?" + +#: src/screens/Onboarding/index.tsx:23 +msgid "Art" +msgstr "藝術" + +#: src/view/com/modals/SelfLabel.tsx:124 +msgid "Artistic or non-erotic nudity." +msgstr "藝術作品或非色情的裸露。" + +#: src/screens/Signup/StepHandle.tsx:119 +msgid "At least 3 characters" +msgstr "至少 3 個字元" + +#: src/components/dms/MessagesListHeader.tsx:75 +#: src/components/moderation/LabelsOnMeDialog.tsx:281 +#: src/components/moderation/LabelsOnMeDialog.tsx:282 +#: src/screens/Login/ChooseAccountForm.tsx:98 +#: src/screens/Login/ChooseAccountForm.tsx:103 +#: src/screens/Login/ForgotPasswordForm.tsx:129 +#: src/screens/Login/ForgotPasswordForm.tsx:135 +#: src/screens/Login/LoginForm.tsx:275 +#: src/screens/Login/LoginForm.tsx:281 +#: src/screens/Login/SetNewPasswordForm.tsx:160 +#: src/screens/Login/SetNewPasswordForm.tsx:166 +#: src/screens/Messages/Conversation/ChatDisabled.tsx:133 +#: src/screens/Messages/Conversation/ChatDisabled.tsx:134 +#: src/screens/Profile/Header/Shell.tsx:102 +#: src/screens/Signup/index.tsx:193 +#: src/view/com/util/ViewHeader.tsx:91 +msgid "Back" +msgstr "返回" + +#: src/view/screens/Settings/index.tsx:496 +msgid "Basics" +msgstr "基本設定" + +#: src/components/dialogs/BirthDateSettings.tsx:107 +msgid "Birthday" +msgstr "生日" + +#: src/view/screens/Settings/index.tsx:377 +msgid "Birthday:" +msgstr "生日:" + +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:314 +#: src/view/com/profile/ProfileMenu.tsx:366 +msgid "Block" +msgstr "封鎖" + +#: src/components/dms/ConvoMenu.tsx:188 +#: src/components/dms/ConvoMenu.tsx:192 +msgid "Block account" +msgstr "封鎖帳號" + +#: src/view/com/profile/ProfileMenu.tsx:305 +#: src/view/com/profile/ProfileMenu.tsx:312 +msgid "Block Account" +msgstr "封鎖帳號" + +#: src/view/com/profile/ProfileMenu.tsx:349 +msgid "Block Account?" +msgstr "封鎖帳號?" + +#: src/view/screens/ProfileList.tsx:584 +msgid "Block accounts" +msgstr "封鎖帳號" + +#: src/view/screens/ProfileList.tsx:688 +msgid "Block list" +msgstr "封鎖列表" + +#: src/view/screens/ProfileList.tsx:683 +msgid "Block these accounts?" +msgstr "封鎖這些帳號?" + +#: src/view/com/lists/ListCard.tsx:112 +#: src/view/com/util/post-embeds/QuoteEmbed.tsx:75 +msgid "Blocked" +msgstr "已被封鎖" + +#: src/screens/Moderation/index.tsx:267 +msgid "Blocked accounts" +msgstr "已封鎖帳號" + +#: src/Navigation.tsx:140 +#: src/view/screens/ModerationBlockedAccounts.tsx:109 +msgid "Blocked Accounts" +msgstr "已封鎖帳號" + +#: src/view/com/profile/ProfileMenu.tsx:361 +msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." +msgstr "被封鎖的帳號無法在您的討論串中回覆、提及您,或以其他方式與您互動。" + +#: src/view/screens/ModerationBlockedAccounts.tsx:117 +msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours." +msgstr "被封鎖的帳號無法在您的討論串中回覆、提及您,或以其他方式與您互動。您將看不到他們的內容,他們也會被阻止看到您的內容。" + +#: src/view/com/post-thread/PostThread.tsx:367 +msgid "Blocked post." +msgstr "已封鎖貼文。" + +#: src/screens/Profile/Sections/Labels.tsx:173 +msgid "Blocking does not prevent this labeler from placing labels on your account." +msgstr "封鎖此帳號不會阻止被貼上標記。" + +#: src/view/screens/ProfileList.tsx:685 +msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." +msgstr "封鎖資訊是公開的。被封鎖的帳號無法在您的討論串中回覆、提及您,或以其他方式與您互動。" + +#: src/view/com/profile/ProfileMenu.tsx:358 +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 "封鎖此帳號不會阻止被貼上標記,但它會阻止此帳號在您的討論串中回覆或與您進行互動。" + +#: src/view/com/auth/SplashScreen.web.tsx:154 +msgid "Blog" +msgstr "部落格" + +#: src/view/com/auth/server-input/index.tsx:89 +#: src/view/com/auth/server-input/index.tsx:91 +msgid "Bluesky" +msgstr "Bluesky" + +#: src/view/com/auth/server-input/index.tsx:154 +msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers." +msgstr "Bluesky 是一個開放的網路,您可以自行挑選託管服務供應商。自定義託管服務現已為開發人員推出測試版。" + +#: src/screens/Moderation/index.tsx:533 +msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private." +msgstr "Bluesky 的官方程式將不會向未登入的使用者顯示您的個人檔案和貼文。但其他應用程式可能不會遵循這個要求,這不會使您的帳號變成非公開的。" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:53 +msgid "Blur images" +msgstr "模糊圖片" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:51 +msgid "Blur images and filter from feeds" +msgstr "模糊圖片並從動態中過濾" + +#: src/screens/Onboarding/index.tsx:30 +msgid "Books" +msgstr "書籍" + +#: src/screens/Home/NoFeedsPinned.tsx:103 +#: src/screens/Home/NoFeedsPinned.tsx:109 +msgid "Browse other feeds" +msgstr "瀏覽其他動態源" + +#: src/view/com/auth/SplashScreen.web.tsx:151 +msgid "Business" +msgstr "商務" + +#: src/view/com/profile/ProfileSubpageHeader.tsx:156 +msgid "by —" +msgstr "來自 —" + +#: src/components/LabelingServiceCard/index.tsx:56 +msgid "By {0}" +msgstr "來自 {0}" + +#: src/view/com/profile/ProfileSubpageHeader.tsx:160 +msgid "by <0/>" +msgstr "來自 <0/>" + +#: src/screens/Signup/StepInfo/Policies.tsx:74 +msgid "By creating an account you agree to the {els}." +msgstr "建立帳號即表示您同意 {els}。" + +#: src/view/com/profile/ProfileSubpageHeader.tsx:158 +msgid "by you" +msgstr "來自您" + +#: src/view/com/composer/photos/OpenCameraBtn.tsx:73 +msgid "Camera" +msgstr "相機" + +#: src/view/com/modals/AddAppPasswords.tsx:179 +msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long." +msgstr "只能包含字母、數字、空格、破折號及底線。長度必須至少 4 個字元,但不超過 32 個字元。" + +#: src/components/Menu/index.tsx:215 +#: src/components/Prompt.tsx:119 +#: src/components/Prompt.tsx:121 +#: src/components/TagMenu/index.tsx:268 +#: src/screens/Deactivated.tsx:161 +#: src/view/com/composer/Composer.tsx:434 +#: src/view/com/composer/Composer.tsx:440 +#: src/view/com/modals/ChangeEmail.tsx:213 +#: src/view/com/modals/ChangeEmail.tsx:215 +#: src/view/com/modals/ChangeHandle.tsx:148 +#: src/view/com/modals/ChangePassword.tsx:268 +#: src/view/com/modals/ChangePassword.tsx:271 +#: src/view/com/modals/CreateOrEditList.tsx:344 +#: src/view/com/modals/crop-image/CropImage.web.tsx:162 +#: src/view/com/modals/EditImage.tsx:324 +#: src/view/com/modals/EditProfile.tsx:250 +#: src/view/com/modals/InAppBrowserConsent.tsx:78 +#: src/view/com/modals/InAppBrowserConsent.tsx:80 +#: src/view/com/modals/LinkWarning.tsx:105 +#: src/view/com/modals/LinkWarning.tsx:107 +#: src/view/com/modals/VerifyEmail.tsx:255 +#: src/view/com/modals/VerifyEmail.tsx:261 +#: src/view/com/util/post-ctrls/RepostButton.tsx:139 +#: src/view/screens/Search/Search.tsx:704 +#: src/view/shell/desktop/Search.tsx:218 +msgid "Cancel" +msgstr "取消" + +#: src/view/com/modals/CreateOrEditList.tsx:349 +#: src/view/com/modals/DeleteAccount.tsx:174 +#: src/view/com/modals/DeleteAccount.tsx:296 +msgctxt "action" +msgid "Cancel" +msgstr "取消" + +#: src/view/com/modals/DeleteAccount.tsx:170 +#: src/view/com/modals/DeleteAccount.tsx:292 +msgid "Cancel account deletion" +msgstr "取消刪除帳號" + +#: src/view/com/modals/ChangeHandle.tsx:144 +msgid "Cancel change handle" +msgstr "取消修改帳號代碼" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:159 +msgid "Cancel image crop" +msgstr "取消圖片裁剪" + +#: src/view/com/modals/EditProfile.tsx:245 +msgid "Cancel profile editing" +msgstr "取消編輯個人檔案" + +#: src/view/com/util/post-ctrls/RepostButton.tsx:133 +msgid "Cancel quote post" +msgstr "取消引用貼文" + +#: src/screens/Deactivated.tsx:155 +msgid "Cancel reactivation and log out" +msgstr "取消重新啟用並登出" + +#: src/view/com/modals/ListAddRemoveUsers.tsx:87 +#: src/view/shell/desktop/Search.tsx:214 +msgid "Cancel search" +msgstr "取消搜尋" + +#: src/view/com/modals/LinkWarning.tsx:106 +msgid "Cancels opening the linked website" +msgstr "取消開啟網站連結" + +#: src/view/com/modals/VerifyEmail.tsx:160 +msgid "Change" +msgstr "變更" + +#: src/view/screens/Settings/index.tsx:371 +msgctxt "action" +msgid "Change" +msgstr "變更" + +#: src/view/screens/Settings/index.tsx:718 +msgid "Change handle" +msgstr "變更帳號代碼" + +#: src/view/com/modals/ChangeHandle.tsx:156 +#: src/view/screens/Settings/index.tsx:729 +msgid "Change Handle" +msgstr "變更帳號代碼" + +#: src/view/com/modals/VerifyEmail.tsx:155 +msgid "Change my email" +msgstr "變更我的電子郵件地址" + +#: src/view/screens/Settings/index.tsx:763 +msgid "Change password" +msgstr "變更密碼" + +#: src/view/com/modals/ChangePassword.tsx:142 +#: src/view/screens/Settings/index.tsx:774 +msgid "Change Password" +msgstr "變更密碼" + +#: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 +msgid "Change post language to {0}" +msgstr "變更貼文的發佈語言為 {0}" + +#: src/view/com/modals/ChangeEmail.tsx:104 +msgid "Change Your Email" +msgstr "變更您的電子郵件地址" + +#: src/Navigation.tsx:308 +#: src/view/shell/bottom-bar/BottomBar.tsx:201 +#: src/view/shell/desktop/LeftNav.tsx:295 +msgid "Chat" +msgstr "對話" + +#: src/components/dms/ConvoMenu.tsx:82 +msgid "Chat muted" +msgstr "對話已靜音" + +#: src/components/dms/ConvoMenu.tsx:112 +#: src/components/dms/MessageMenu.tsx:81 +#: src/Navigation.tsx:313 +#: src/screens/Messages/List/index.tsx:88 +#: src/view/screens/Settings/index.tsx:638 +msgid "Chat settings" +msgstr "對話設定" + +#: src/screens/Messages/Settings.tsx:59 +#: src/view/screens/Settings/index.tsx:647 +msgid "Chat Settings" +msgstr "對話設定" + +#: src/components/dms/ConvoMenu.tsx:84 +msgid "Chat unmuted" +msgstr "對話已解除靜音" + +#: src/screens/SignupQueued.tsx:78 +#: src/screens/SignupQueued.tsx:82 +msgid "Check my status" +msgstr "檢查我的狀態" + +#: src/screens/Login/LoginForm.tsx:268 +msgid "Check your email for a login code and enter it here." +msgstr "在此輸入寄送至您電子郵件地址的驗證碼。" + +#: src/view/com/modals/DeleteAccount.tsx:231 +msgid "Check your inbox for an email with the confirmation code to enter below:" +msgstr "在下方輸入寄送至您電子郵件地址的驗證碼:" + +#: src/view/com/modals/Threadgate.tsx:75 +msgid "Choose \"Everybody\" or \"Nobody\"" +msgstr "選擇「所有人」或「沒有人」" + +#: src/view/com/auth/server-input/index.tsx:79 +msgid "Choose Service" +msgstr "選擇服務" + +#: src/screens/Onboarding/StepFinished.tsx:168 +msgid "Choose the algorithms that power your custom feeds." +msgstr "選擇提供您自定義動態的演算法。" + +#: src/screens/Onboarding/StepProfile/AvatarCreatorItems.tsx:107 +msgid "Choose this color as your avatar" +msgstr "選擇這個顏色作為您的頭像" + +#: src/screens/Signup/StepInfo/index.tsx:114 +msgid "Choose your password" +msgstr "選擇您的密碼" + +#: src/view/screens/Settings/index.tsx:910 +msgid "Clear all legacy storage data" +msgstr "清除所有遺留資料" + +#: src/view/screens/Settings/index.tsx:913 +msgid "Clear all legacy storage data (restart after this)" +msgstr "清除所有遺留資料(並重啟)" + +#: src/view/screens/Settings/index.tsx:922 +msgid "Clear all storage data" +msgstr "清除所有資料" + +#: src/view/screens/Settings/index.tsx:925 +msgid "Clear all storage data (restart after this)" +msgstr "清除所有資料(並重啟)" + +#: src/view/com/util/forms/SearchInput.tsx:88 +#: src/view/screens/Search/Search.tsx:824 +msgid "Clear search query" +msgstr "清除搜尋記錄" + +#: src/view/screens/Settings/index.tsx:911 +msgid "Clears all legacy storage data" +msgstr "清除所有遺留資料" + +#: src/view/screens/Settings/index.tsx:923 +msgid "Clears all storage data" +msgstr "清除所有資料" + +#: src/view/screens/Support.tsx:40 +msgid "click here" +msgstr "點擊這裡" + +#: src/view/com/modals/DeleteAccount.tsx:208 +msgid "Click here for more information on deactivating your account" +msgstr "點擊這裡以瞭解有關停用帳號的詳細資訊" + +#: src/view/com/modals/DeleteAccount.tsx:216 +msgid "Click here for more information." +msgstr "點擊這裡以瞭解更多資訊。" + +#: src/components/TagMenu/index.web.tsx:138 +msgid "Click here to open tag menu for {tag}" +msgstr "點擊這裡以開啟 {tag} 的標籤選單" + +#: src/components/dms/MessageItem.tsx:237 +msgid "Click to retry failed message" +msgstr "點擊以重試傳送訊息" + +#: src/screens/Onboarding/index.tsx:32 +msgid "Climate" +msgstr "氣象" + +#: src/components/dms/ChatEmptyPill.tsx:39 +msgid "Clip 🐴 clop 🐴" +msgstr "達達的馬蹄🐴是美麗的錯誤🐴" + +#: src/components/dialogs/GifSelect.ios.tsx:250 +#: src/components/dialogs/GifSelect.tsx:268 +#: src/components/dms/dialogs/SearchablePeopleList.tsx:261 +#: src/view/com/modals/ChangePassword.tsx:268 +#: src/view/com/modals/ChangePassword.tsx:271 +#: src/view/com/util/post-embeds/GifEmbed.tsx:189 +msgid "Close" +msgstr "關閉" + +#: src/components/Dialog/index.web.tsx:113 +#: src/components/Dialog/index.web.tsx:251 +msgid "Close active dialog" +msgstr "關閉打開的對話框" + +#: src/screens/Login/PasswordUpdatedForm.tsx:38 +msgid "Close alert" +msgstr "關閉警告" + +#: src/view/com/util/BottomSheetCustomBackdrop.tsx:36 +msgid "Close bottom drawer" +msgstr "關閉底欄" + +#: src/components/dialogs/GifSelect.ios.tsx:244 +#: src/components/dialogs/GifSelect.tsx:262 +msgid "Close dialog" +msgstr "關閉對話框" + +#: src/components/dialogs/GifSelect.tsx:161 +msgid "Close GIF dialog" +msgstr "關閉 GIF 對話框" + +#: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:36 +msgid "Close image" +msgstr "關閉圖片" + +#: src/view/com/lightbox/Lightbox.web.tsx:129 +msgid "Close image viewer" +msgstr "關閉圖片檢視器" + +#: src/components/dms/MessagesNUX.tsx:162 +msgid "Close modal" +msgstr "關閉視窗" + +#: src/view/shell/index.web.tsx:61 +msgid "Close navigation footer" +msgstr "關閉導覽頁腳" + +#: src/components/Menu/index.tsx:209 +#: src/components/TagMenu/index.tsx:262 +msgid "Close this dialog" +msgstr "關閉此對話框" + +#: src/view/shell/index.web.tsx:62 +msgid "Closes bottom navigation bar" +msgstr "關閉底部導覽列" + +#: src/screens/Login/PasswordUpdatedForm.tsx:39 +msgid "Closes password update alert" +msgstr "關閉密碼更新警告" + +#: src/view/com/composer/Composer.tsx:436 +msgid "Closes post composer and discards post draft" +msgstr "關閉貼文編輯頁並捨棄草稿" + +#: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:37 +msgid "Closes viewer for header image" +msgstr "關閉標題圖片檢視器" + +#: src/view/com/notifications/FeedItem.tsx:207 +msgid "Collapse list of users" +msgstr "折疊用戶清單" + +#: src/view/com/notifications/FeedItem.tsx:343 +msgid "Collapses list of users for a given notification" +msgstr "折疊指定通知的用戶清單" + +#: src/screens/Onboarding/index.tsx:38 +msgid "Comedy" +msgstr "喜劇" + +#: src/screens/Onboarding/index.tsx:24 +msgid "Comics" +msgstr "漫畫" + +#: src/Navigation.tsx:254 +#: src/view/screens/CommunityGuidelines.tsx:32 +msgid "Community Guidelines" +msgstr "社群守則" + +#: src/screens/Onboarding/StepFinished.tsx:181 +msgid "Complete onboarding and start using your account" +msgstr "完成初始設定並開始使用您的帳號" + +#: src/screens/Signup/index.tsx:168 +msgid "Complete the challenge" +msgstr "完成驗證" + +#: src/view/com/composer/Composer.tsx:553 +msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" +msgstr "撰寫貼文的長度最多為 {MAX_GRAPHEME_LENGTH} 個字元" + +#: src/view/com/composer/Prompt.tsx:26 +msgid "Compose reply" +msgstr "撰寫回覆" + +#: src/components/moderation/LabelPreference.tsx:81 +msgid "Configure content filtering setting for category: {name}" +msgstr "為 {name} 配置內容過濾設定" + +#: src/components/moderation/LabelPreference.tsx:244 +msgid "Configured in <0>moderation settings." +msgstr "已在<0>內容管理設定中配置。" + +#: src/components/Prompt.tsx:162 +#: src/components/Prompt.tsx:165 +#: src/view/com/modals/SelfLabel.tsx:155 +#: src/view/com/modals/VerifyEmail.tsx:239 +#: src/view/com/modals/VerifyEmail.tsx:241 +#: src/view/screens/PreferencesFollowingFeed.tsx:307 +#: src/view/screens/PreferencesThreads.tsx:159 +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:180 +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:183 +msgid "Confirm" +msgstr "確認" + +#: src/view/com/modals/ChangeEmail.tsx:188 +#: src/view/com/modals/ChangeEmail.tsx:190 +msgid "Confirm Change" +msgstr "確認更改" + +#: src/view/com/modals/lang-settings/ConfirmLanguagesButton.tsx:35 +msgid "Confirm content language settings" +msgstr "確認內容語言設定" + +#: src/view/com/modals/DeleteAccount.tsx:282 +msgid "Confirm delete account" +msgstr "確認刪除帳號" + +#: src/screens/Moderation/index.tsx:301 +msgid "Confirm your age:" +msgstr "確認您的年齡:" + +#: src/screens/Moderation/index.tsx:292 +msgid "Confirm your birthdate" +msgstr "確認您的出生日期" + +#: src/screens/Login/LoginForm.tsx:250 +#: src/view/com/modals/ChangeEmail.tsx:152 +#: src/view/com/modals/DeleteAccount.tsx:238 +#: src/view/com/modals/DeleteAccount.tsx:244 +#: src/view/com/modals/VerifyEmail.tsx:173 +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:143 +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:149 +msgid "Confirmation code" +msgstr "驗證碼" + +#: src/screens/Login/LoginForm.tsx:302 +msgid "Connecting..." +msgstr "連線中…" + +#: src/screens/Signup/index.tsx:238 +msgid "Contact support" +msgstr "聯繫支援" + +#: src/lib/moderation/useGlobalLabelStrings.ts:18 +msgid "Content Blocked" +msgstr "已封鎖內容" + +#: src/screens/Moderation/index.tsx:285 +msgid "Content filters" +msgstr "內容過濾" + +#: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:74 +#: src/view/screens/LanguageSettings.tsx:280 +msgid "Content Languages" +msgstr "內容語言" + +#: src/components/moderation/ModerationDetailsDialog.tsx:75 +#: src/lib/moderation/useModerationCauseDescription.ts:77 +msgid "Content Not Available" +msgstr "內容不可用" + +#: src/components/moderation/ModerationDetailsDialog.tsx:46 +#: src/components/moderation/ScreenHider.tsx:99 +#: src/lib/moderation/useGlobalLabelStrings.ts:22 +#: src/lib/moderation/useModerationCauseDescription.ts:40 +msgid "Content Warning" +msgstr "內容警告" + +#: src/view/com/composer/labels/LabelsBtn.tsx:32 +msgid "Content warnings" +msgstr "內容警告" + +#: src/components/Menu/index.web.tsx:83 +msgid "Context menu backdrop, click to close the menu." +msgstr "彈出式選單背景,點擊以關閉選單。" + +#: src/screens/Onboarding/StepInterests/index.tsx:253 +#: src/screens/Onboarding/StepProfile/index.tsx:269 +msgid "Continue" +msgstr "繼續" + +#: src/components/AccountList.tsx:113 +msgid "Continue as {0} (currently signed in)" +msgstr "以 {0} 繼續 (目前已登入)" + +#: src/view/com/post-thread/PostThreadLoadMore.tsx:52 +msgid "Continue thread..." +msgstr "繼續載入討論串…" + +#: src/screens/Onboarding/StepInterests/index.tsx:250 +#: src/screens/Onboarding/StepProfile/index.tsx:266 +#: src/screens/Signup/index.tsx:213 +msgid "Continue to next step" +msgstr "繼續下一步" + +#: src/screens/Messages/List/ChatListItem.tsx:154 +msgid "Conversation deleted" +msgstr "對話已刪除" + +#: src/screens/Onboarding/index.tsx:41 +msgid "Cooking" +msgstr "烹飪" + +#: src/view/com/modals/AddAppPasswords.tsx:220 +#: src/view/com/modals/InviteCodes.tsx:183 +msgid "Copied" +msgstr "已複製" + +#: src/view/screens/Settings/index.tsx:263 +msgid "Copied build version to clipboard" +msgstr "已複製建構版本號至剪貼簿" + +#: src/components/dms/MessageMenu.tsx:57 +#: src/view/com/modals/AddAppPasswords.tsx:80 +#: src/view/com/modals/ChangeHandle.tsx:320 +#: src/view/com/modals/InviteCodes.tsx:153 +#: src/view/com/util/forms/PostDropdownBtn.tsx:189 +msgid "Copied to clipboard" +msgstr "已複製至剪貼簿" + +#: src/components/dialogs/Embed.tsx:134 +msgid "Copied!" +msgstr "已複製!" + +#: src/view/com/modals/AddAppPasswords.tsx:214 +msgid "Copies app password" +msgstr "複製應用程式專用密碼" + +#: src/view/com/modals/AddAppPasswords.tsx:213 +msgid "Copy" +msgstr "複製" + +#: src/view/com/modals/ChangeHandle.tsx:474 +msgid "Copy {0}" +msgstr "複製{0}" + +#: src/components/dialogs/Embed.tsx:120 +#: src/components/dialogs/Embed.tsx:139 +msgid "Copy code" +msgstr "複製程式碼" + +#: src/view/screens/ProfileList.tsx:428 +msgid "Copy link to list" +msgstr "複製列表連結" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:307 +#: src/view/com/util/forms/PostDropdownBtn.tsx:316 +msgid "Copy link to post" +msgstr "複製貼文連結" + +#: src/components/dms/MessageMenu.tsx:110 +#: src/components/dms/MessageMenu.tsx:112 +msgid "Copy message text" +msgstr "複製訊息文字" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:285 +#: src/view/com/util/forms/PostDropdownBtn.tsx:287 +msgid "Copy post text" +msgstr "複製貼文文字" + +#: src/Navigation.tsx:259 +#: src/view/screens/CopyrightPolicy.tsx:29 +msgid "Copyright Policy" +msgstr "著作權政策" + +#: src/components/dms/LeaveConvoPrompt.tsx:39 +msgid "Could not leave chat" +msgstr "無法離開對話" + +#: src/view/screens/ProfileFeed.tsx:102 +msgid "Could not load feed" +msgstr "無法載入動態" + +#: src/view/screens/ProfileList.tsx:961 +msgid "Could not load list" +msgstr "無法載入列表" + +#: src/components/dms/ConvoMenu.tsx:88 +msgid "Could not mute chat" +msgstr "無法靜音對話" + +#: src/view/com/auth/SplashScreen.tsx:57 +#: src/view/com/auth/SplashScreen.web.tsx:106 +msgid "Create a new account" +msgstr "建立新帳號" + +#: src/view/screens/Settings/index.tsx:423 +msgid "Create a new Bluesky account" +msgstr "建立新的 Bluesky 帳號" + +#: src/screens/Signup/index.tsx:141 +msgid "Create Account" +msgstr "建立帳號" + +#: src/components/dialogs/Signin.tsx:86 +#: src/components/dialogs/Signin.tsx:88 +msgid "Create an account" +msgstr "建立一個帳號" + +#: src/screens/Onboarding/StepProfile/index.tsx:283 +msgid "Create an avatar instead" +msgstr "或是建立一個頭像" + +#: src/view/com/modals/AddAppPasswords.tsx:242 +msgid "Create App Password" +msgstr "建立應用程式專用密碼" + +#: src/view/com/auth/SplashScreen.tsx:48 +#: src/view/com/auth/SplashScreen.web.tsx:97 +msgid "Create new account" +msgstr "建立新帳號" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:98 +msgid "Create report for {0}" +msgstr "建立 {0} 的檢舉" + +#: src/view/screens/AppPasswords.tsx:251 +msgid "Created {0}" +msgstr "{0} 已建立" + +#: src/screens/Onboarding/index.tsx:26 +msgid "Culture" +msgstr "文化" + +#: src/view/com/auth/server-input/index.tsx:97 +#: src/view/com/auth/server-input/index.tsx:99 +msgid "Custom" +msgstr "自訂" + +#: src/view/com/modals/ChangeHandle.tsx:382 +msgid "Custom domain" +msgstr "自訂網域" + +#: src/view/screens/Feeds.tsx:763 +#: src/view/screens/Search/Explore.tsx:383 +msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." +msgstr "由社群打造的自訂動態源帶來全新體驗,幫助您找到所愛的內容。" + +#: src/view/screens/PreferencesExternalEmbeds.tsx:56 +msgid "Customize media from external sites." +msgstr "自訂外部網站的媒體。" + +#: src/view/screens/Settings/index.tsx:458 +#: src/view/screens/Settings/index.tsx:484 +msgid "Dark" +msgstr "深色" + +#: src/view/screens/Debug.tsx:63 +msgid "Dark mode" +msgstr "深色模式" + +#: src/view/screens/Settings/index.tsx:471 +msgid "Dark Theme" +msgstr "深色主題" + +#: src/screens/Signup/StepInfo/index.tsx:134 +msgid "Date of birth" +msgstr "出生日期" + +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:73 +#: src/view/screens/Settings/index.tsx:806 +msgid "Deactivate account" +msgstr "停用帳號" + +#: src/view/screens/Settings/index.tsx:818 +msgid "Deactivate my account" +msgstr "停用我的帳號" + +#: src/view/screens/Settings/index.tsx:873 +msgid "Debug Moderation" +msgstr "內容管理偵錯" + +#: src/view/screens/Debug.tsx:83 +msgid "Debug panel" +msgstr "偵錯面板" + +#: src/components/dms/MessageMenu.tsx:151 +#: src/view/com/util/forms/PostDropdownBtn.tsx:433 +#: src/view/screens/AppPasswords.tsx:285 +#: src/view/screens/ProfileList.tsx:667 +msgid "Delete" +msgstr "刪除" + +#: src/view/screens/Settings/index.tsx:828 +msgid "Delete account" +msgstr "刪除帳號" + +#: src/view/com/modals/DeleteAccount.tsx:105 +msgid "Delete Account <0>\"<1>{0}<2>\"" +msgstr "刪除帳號 <0>「<1>{0}<2>」" + +#: src/view/screens/AppPasswords.tsx:244 +msgid "Delete app password" +msgstr "刪除應用程式專用密碼" + +#: src/view/screens/AppPasswords.tsx:280 +msgid "Delete app password?" +msgstr "刪除應用程式專用密碼?" + +#: src/view/screens/Settings/index.tsx:890 +#: src/view/screens/Settings/index.tsx:893 +msgid "Delete chat declaration record" +msgstr "刪除對話聲明紀錄" + +#: src/components/dms/MessageMenu.tsx:124 +msgid "Delete for me" +msgstr "為我刪除" + +#: src/view/screens/ProfileList.tsx:471 +msgid "Delete List" +msgstr "刪除列表" + +#: src/components/dms/MessageMenu.tsx:147 +msgid "Delete message" +msgstr "刪除訊息" + +#: src/components/dms/MessageMenu.tsx:122 +msgid "Delete message for me" +msgstr "為我刪除訊息" + +#: src/view/com/modals/DeleteAccount.tsx:285 +msgid "Delete my account" +msgstr "刪除我的帳號" + +#: src/view/screens/Settings/index.tsx:840 +msgid "Delete My Account…" +msgstr "刪除我的帳號…" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:414 +#: src/view/com/util/forms/PostDropdownBtn.tsx:416 +msgid "Delete post" +msgstr "刪除貼文" + +#: src/view/screens/ProfileList.tsx:662 +msgid "Delete this list?" +msgstr "刪除此列表?" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:428 +msgid "Delete this post?" +msgstr "刪除這條貼文?" + +#: src/view/com/util/post-embeds/QuoteEmbed.tsx:84 +msgid "Deleted" +msgstr "已刪除" + +#: src/view/com/post-thread/PostThread.tsx:353 +msgid "Deleted post." +msgstr "已刪除的貼文。" + +#: src/view/screens/Settings/index.tsx:891 +msgid "Deletes the chat declaration record" +msgstr "刪除對話聲明紀錄" + +#: src/view/com/modals/CreateOrEditList.tsx:289 +#: src/view/com/modals/CreateOrEditList.tsx:310 +#: src/view/com/modals/EditProfile.tsx:199 +#: src/view/com/modals/EditProfile.tsx:211 +msgid "Description" +msgstr "描述" + +#: src/view/com/composer/GifAltText.tsx:140 +msgid "Descriptive alt text" +msgstr "生動的替代文字" + +#: src/view/com/composer/Composer.tsx:277 +msgid "Did you want to say anything?" +msgstr "有什麼想說的嗎?" + +#: src/view/screens/Settings/index.tsx:477 +msgid "Dim" +msgstr "昏暗" + +#: src/components/dms/MessagesNUX.tsx:88 +msgid "Direct messages are here!" +msgstr "私人訊息已推出!" + +#: src/view/screens/AccessibilitySettings.tsx:107 +msgid "Disable autoplay for GIFs" +msgstr "關閉 GIF 自動播放" + +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:90 +msgid "Disable Email 2FA" +msgstr "關閉電子郵件雙重驗證" + +#: src/view/screens/AccessibilitySettings.tsx:121 +msgid "Disable haptic feedback" +msgstr "關閉觸覺回饋" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:32 +#: src/lib/moderation/useLabelBehaviorDescription.ts:42 +#: src/lib/moderation/useLabelBehaviorDescription.ts:68 +#: src/screens/Messages/Settings.tsx:140 +#: src/screens/Messages/Settings.tsx:143 +#: src/screens/Moderation/index.tsx:341 +msgid "Disabled" +msgstr "停用" + +#: src/view/com/composer/Composer.tsx:634 +msgid "Discard" +msgstr "捨棄" + +#: src/view/com/composer/Composer.tsx:631 +msgid "Discard draft?" +msgstr "捨棄草稿?" + +#: src/screens/Moderation/index.tsx:518 +#: src/screens/Moderation/index.tsx:522 +msgid "Discourage apps from showing my account to logged-out users" +msgstr "阻撓應用程式向未登入用戶顯示我的帳號" + +#: src/view/com/posts/FollowingEmptyState.tsx:70 +#: src/view/com/posts/FollowingEndOfFeed.tsx:71 +msgid "Discover new custom feeds" +msgstr "探索新的自訂動態源" + +#: src/view/screens/Search/Explore.tsx:381 +msgid "Discover new feeds" +msgstr "探索新的動態源" + +#: src/view/screens/Feeds.tsx:760 +msgid "Discover New Feeds" +msgstr "探索新的動態源" + +#: src/view/screens/AccessibilitySettings.tsx:95 +msgid "Display larger alt text badges" +msgstr "顯示更大的 alt 文本標識" + +#: src/view/com/modals/EditProfile.tsx:193 +msgid "Display name" +msgstr "顯示名稱" + +#: src/view/com/modals/EditProfile.tsx:181 +msgid "Display Name" +msgstr "顯示名稱" + +#: src/view/com/modals/ChangeHandle.tsx:391 +msgid "DNS Panel" +msgstr "DNS 控制台" + +#: src/lib/moderation/useGlobalLabelStrings.ts:39 +msgid "Does not include nudity." +msgstr "不包含裸露內容。" + +#: src/screens/Signup/StepHandle.tsx:105 +msgid "Doesn't begin or end with a hyphen" +msgstr "不以連字符開頭或結尾" + +#: src/view/com/modals/ChangeHandle.tsx:475 +msgid "Domain Value" +msgstr "網域設定值" + +#: src/view/com/modals/ChangeHandle.tsx:482 +msgid "Domain verified!" +msgstr "網域已驗證!" + +#: src/components/dialogs/BirthDateSettings.tsx:119 +#: src/components/dialogs/BirthDateSettings.tsx:125 +#: src/components/forms/DateField/index.tsx:77 +#: src/components/forms/DateField/index.tsx:83 +#: src/screens/Onboarding/StepProfile/index.tsx:322 +#: src/screens/Onboarding/StepProfile/index.tsx:325 +#: src/view/com/auth/server-input/index.tsx:169 +#: src/view/com/auth/server-input/index.tsx:170 +#: src/view/com/modals/AddAppPasswords.tsx:242 +#: src/view/com/modals/AltImage.tsx:141 +#: src/view/com/modals/crop-image/CropImage.web.tsx:177 +#: src/view/com/modals/InviteCodes.tsx:81 +#: src/view/com/modals/InviteCodes.tsx:124 +#: src/view/com/modals/ListAddRemoveUsers.tsx:142 +#: src/view/screens/PreferencesFollowingFeed.tsx:310 +msgid "Done" +msgstr "完成" + +#: src/view/com/modals/EditImage.tsx:334 +#: src/view/com/modals/ListAddRemoveUsers.tsx:144 +#: src/view/com/modals/SelfLabel.tsx:158 +#: src/view/com/modals/Threadgate.tsx:133 +#: src/view/com/modals/Threadgate.tsx:136 +#: src/view/com/modals/UserAddRemoveLists.tsx:108 +#: src/view/com/modals/UserAddRemoveLists.tsx:111 +#: src/view/screens/PreferencesThreads.tsx:162 +msgctxt "action" +msgid "Done" +msgstr "完成" + +#: src/view/com/modals/lang-settings/ConfirmLanguagesButton.tsx:43 +msgid "Done{extraText}" +msgstr "完成{extraText}" + +#: src/view/screens/Settings/ExportCarDialog.tsx:77 +#: src/view/screens/Settings/ExportCarDialog.tsx:81 +msgid "Download CAR file" +msgstr "下載 CAR 檔案" + +#: src/view/com/composer/text-input/TextInput.web.tsx:272 +msgid "Drop to add images" +msgstr "拖放即可新增圖片" + +#: src/view/com/modals/ChangeHandle.tsx:252 +msgid "e.g. alice" +msgstr "例如:alice" + +#: src/view/com/modals/EditProfile.tsx:186 +msgid "e.g. Alice Roberts" +msgstr "例如:張藍天" + +#: src/view/com/modals/ChangeHandle.tsx:374 +msgid "e.g. alice.com" +msgstr "例如:alice.com" + +#: src/view/com/modals/EditProfile.tsx:204 +msgid "e.g. Artist, dog-lover, and avid reader." +msgstr "例如:藝術家、愛狗人士和狂熱讀者。" + +#: src/lib/moderation/useGlobalLabelStrings.ts:43 +msgid "E.g. artistic nudes." +msgstr "例如:藝術裸露。" + +#: src/view/com/modals/CreateOrEditList.tsx:272 +msgid "e.g. Great Posters" +msgstr "例如:優秀的發文者" + +#: src/view/com/modals/CreateOrEditList.tsx:273 +msgid "e.g. Spammers" +msgstr "例如:垃圾內容製造者" + +#: src/view/com/modals/CreateOrEditList.tsx:301 +msgid "e.g. The posters who never miss." +msgstr "例如:絕對不容錯過的發文者。" + +#: src/view/com/modals/CreateOrEditList.tsx:302 +msgid "e.g. Users that repeatedly reply with ads." +msgstr "例如:多次張貼廣告的用戶。" + +#: src/view/com/modals/InviteCodes.tsx:97 +msgid "Each code works once. You'll receive more invite codes periodically." +msgstr "每個邀請碼僅能使用一次。您將定期收到更多的邀請碼。" + +#: src/view/com/lists/ListMembers.tsx:149 +msgctxt "action" +msgid "Edit" +msgstr "編輯" + +#: src/view/screens/Feeds.tsx:370 +#: src/view/screens/Feeds.tsx:441 +msgid "Edit" +msgstr "編輯" + +#: src/view/com/util/UserAvatar.tsx:325 +#: src/view/com/util/UserBanner.tsx:92 +msgid "Edit avatar" +msgstr "編輯頭像" + +#: src/view/com/composer/photos/Gallery.tsx:151 +#: src/view/com/modals/EditImage.tsx:208 +msgid "Edit image" +msgstr "編輯圖片" + +#: src/view/screens/ProfileList.tsx:459 +msgid "Edit list details" +msgstr "編輯列表詳情" + +#: src/view/com/modals/CreateOrEditList.tsx:239 +msgid "Edit Moderation List" +msgstr "編輯內容管理列表" + +#: src/Navigation.tsx:269 +#: src/view/screens/Feeds.tsx:368 +#: src/view/screens/Feeds.tsx:439 +#: src/view/screens/SavedFeeds.tsx:93 +msgid "Edit My Feeds" +msgstr "編輯我的動態源" + +#: src/view/com/modals/EditProfile.tsx:153 +msgid "Edit my profile" +msgstr "編輯我的個人檔案" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:181 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:179 +msgid "Edit profile" +msgstr "編輯個人檔案" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:184 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:182 +msgid "Edit Profile" +msgstr "編輯個人檔案" + +#: src/view/com/modals/CreateOrEditList.tsx:234 +msgid "Edit User List" +msgstr "編輯用戶列表" + +#: src/view/com/threadgate/WhoCanReply.tsx:73 +#: src/view/com/threadgate/WhoCanReply.tsx:130 +msgid "Edit who can reply" +msgstr "編輯「誰可以回覆」" + +#: src/view/com/modals/EditProfile.tsx:194 +msgid "Edit your display name" +msgstr "編輯您的顯示名稱" + +#: src/view/com/modals/EditProfile.tsx:212 +msgid "Edit your profile description" +msgstr "編輯您的帳號描述" + +#: src/screens/Onboarding/index.tsx:31 +msgid "Education" +msgstr "教育" + +#: src/screens/Signup/StepInfo/index.tsx:80 +#: src/view/com/modals/ChangeEmail.tsx:136 +msgid "Email" +msgstr "電子郵件" + +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:64 +msgid "Email 2FA disabled" +msgstr "已關閉電子郵件雙重驗證" + +#: src/screens/Login/ForgotPasswordForm.tsx:99 +msgid "Email address" +msgstr "電子郵件地址" + +#: src/view/com/modals/ChangeEmail.tsx:54 +#: src/view/com/modals/ChangeEmail.tsx:83 +msgid "Email updated" +msgstr "電子郵件已更新" + +#: src/view/com/modals/ChangeEmail.tsx:106 +msgid "Email Updated" +msgstr "電子郵件已更新" + +#: src/view/com/modals/VerifyEmail.tsx:85 +msgid "Email verified" +msgstr "電子郵件已驗證" + +#: src/view/screens/Settings/index.tsx:349 +msgid "Email:" +msgstr "電子郵件:" + +#: src/components/dialogs/Embed.tsx:112 +msgid "Embed HTML code" +msgstr "嵌入 HTML 程式碼" + +#: src/components/dialogs/Embed.tsx:97 +#: src/view/com/util/forms/PostDropdownBtn.tsx:324 +#: src/view/com/util/forms/PostDropdownBtn.tsx:326 +msgid "Embed post" +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 "將這則貼文嵌入到您的網站。只需複製以下程式碼片段,並將其貼上到您網站的 HTML 程式碼中即可。" + +#: src/components/dialogs/EmbedConsent.tsx:101 +msgid "Enable {0} only" +msgstr "僅啟用 {0}" + +#: src/screens/Moderation/index.tsx:329 +msgid "Enable adult content" +msgstr "顯示成人內容" + +#: src/components/dialogs/EmbedConsent.tsx:82 +#: src/components/dialogs/EmbedConsent.tsx:89 +msgid "Enable external media" +msgstr "啟用外部媒體" + +#: src/view/screens/PreferencesExternalEmbeds.tsx:76 +msgid "Enable media players for" +msgstr "啟用媒體播放器" + +#: src/view/screens/PreferencesFollowingFeed.tsx:146 +msgid "Enable this setting to only see replies between people you follow." +msgstr "啟用此設定將只顯示您跟隨的人之間的回覆。" + +#: src/components/dialogs/EmbedConsent.tsx:94 +msgid "Enable this source only" +msgstr "僅啟用此來源" + +#: src/screens/Messages/Settings.tsx:131 +#: src/screens/Messages/Settings.tsx:134 +#: src/screens/Moderation/index.tsx:339 +msgid "Enabled" +msgstr "啟用" + +#: src/screens/Profile/Sections/Feed.tsx:104 +msgid "End of feed" +msgstr "已經到底部啦!" + +#: src/view/com/modals/AddAppPasswords.tsx:160 +msgid "Enter a name for this App Password" +msgstr "輸入此應用程式專用密碼的名稱" + +#: src/screens/Login/SetNewPasswordForm.tsx:139 +msgid "Enter a password" +msgstr "輸入密碼" + +#: src/components/dialogs/MutedWords.tsx:99 +#: src/components/dialogs/MutedWords.tsx:100 +msgid "Enter a word or tag" +msgstr "輸入文字或標籤" + +#: src/view/com/modals/VerifyEmail.tsx:113 +msgid "Enter Confirmation Code" +msgstr "輸入驗證碼" + +#: src/view/com/modals/ChangePassword.tsx:154 +msgid "Enter the code you received to change your password." +msgstr "輸入您收到的驗證碼以更改密碼。" + +#: src/view/com/modals/ChangeHandle.tsx:364 +msgid "Enter the domain you want to use" +msgstr "輸入您想使用的網域" + +#: src/screens/Login/ForgotPasswordForm.tsx:119 +msgid "Enter the email you used to create your account. We'll send you a \"reset code\" so you can set a new password." +msgstr "輸入您用於建立帳號的電子郵件。我們將向您發送一個「重設碼」,來讓您設定新密碼。" + +#: src/components/dialogs/BirthDateSettings.tsx:108 +msgid "Enter your birth date" +msgstr "輸入您的出生日期" + +#: src/screens/Login/ForgotPasswordForm.tsx:105 +#: src/screens/Signup/StepInfo/index.tsx:92 +msgid "Enter your email address" +msgstr "輸入您的電子郵件地址" + +#: src/view/com/modals/ChangeEmail.tsx:42 +msgid "Enter your new email above" +msgstr "請在上方輸入您的新電子郵件地址" + +#: src/view/com/modals/ChangeEmail.tsx:112 +msgid "Enter your new email address below." +msgstr "請在下方輸入您的新電子郵件地址。" + +#: src/screens/Login/index.tsx:101 +msgid "Enter your username and password" +msgstr "輸入您的用戶名稱和密碼" + +#: src/view/screens/Settings/ExportCarDialog.tsx:46 +msgid "Error occurred while saving file" +msgstr "儲存檔案時發生錯誤" + +#: src/screens/Signup/StepCaptcha/index.tsx:51 +msgid "Error receiving captcha response." +msgstr "Captcha 給出了錯誤的回應。" + +#: src/screens/Onboarding/StepInterests/index.tsx:192 +#: src/view/screens/Search/Search.tsx:116 +msgid "Error:" +msgstr "錯誤:" + +#: src/view/com/modals/Threadgate.tsx:79 +msgid "Everybody" +msgstr "所有人" + +#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:46 +#: src/view/com/threadgate/WhoCanReply.tsx:64 +#: src/view/com/threadgate/WhoCanReply.tsx:121 +#: src/view/com/threadgate/WhoCanReply.tsx:235 +msgid "Everybody can reply" +msgstr "所有人都可以回覆" + +#: src/components/dms/MessagesNUX.tsx:131 +#: src/components/dms/MessagesNUX.tsx:134 +#: src/screens/Messages/Settings.tsx:75 +#: src/screens/Messages/Settings.tsx:78 +msgid "Everyone" +msgstr "所有人" + +#: src/lib/moderation/useReportOptions.ts:67 +msgid "Excessive mentions or replies" +msgstr "過多的提及或回覆" + +#: src/lib/moderation/useReportOptions.ts:80 +msgid "Excessive or unwanted messages" +msgstr "過多或不受歡迎的訊息" + +#: src/view/com/modals/DeleteAccount.tsx:293 +msgid "Exits account deletion process" +msgstr "離開刪除帳號流程" + +#: src/view/com/modals/ChangeHandle.tsx:145 +msgid "Exits handle change process" +msgstr "離開修改帳號代碼流程" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:160 +msgid "Exits image cropping process" +msgstr "離開圖片裁剪流程" + +#: src/view/com/lightbox/Lightbox.web.tsx:130 +msgid "Exits image view" +msgstr "離開圖片檢視器" + +#: src/view/com/modals/ListAddRemoveUsers.tsx:88 +#: src/view/shell/desktop/Search.tsx:215 +msgid "Exits inputting search query" +msgstr "退出輸入搜索查詢" + +#: src/view/com/lightbox/Lightbox.web.tsx:183 +msgid "Expand alt text" +msgstr "展開替代文字" + +#: src/view/com/notifications/FeedItem.tsx:208 +msgid "Expand list of users" +msgstr "展開用戶清單" + +#: src/view/com/composer/ComposerReplyTo.tsx:82 +#: src/view/com/composer/ComposerReplyTo.tsx:85 +msgid "Expand or collapse the full post you are replying to" +msgstr "展開或摺疊您正在回覆的完整貼文" + +#: src/lib/moderation/useGlobalLabelStrings.ts:47 +msgid "Explicit or potentially disturbing media." +msgstr "露骨或可能令人不安的媒體內容。" + +#: src/lib/moderation/useGlobalLabelStrings.ts:35 +msgid "Explicit sexual images." +msgstr "露骨的色情圖片。" + +#: src/view/screens/Settings/index.tsx:786 +msgid "Export my data" +msgstr "匯出我的資料" + +#: src/view/screens/Settings/ExportCarDialog.tsx:62 +#: src/view/screens/Settings/index.tsx:797 +msgid "Export My Data" +msgstr "匯出我的資料" + +#: src/components/dialogs/EmbedConsent.tsx:55 +#: src/components/dialogs/EmbedConsent.tsx:59 +msgid "External Media" +msgstr "外部媒體" + +#: src/components/dialogs/EmbedConsent.tsx:71 +#: src/view/screens/PreferencesExternalEmbeds.tsx:67 +msgid "External media may allow websites to collect information about you and your device. No information is sent or requested until you press the \"play\" button." +msgstr "外部媒體可能允許網站收集有關您和您裝置的資料。在您按下「播放」按鈕之前,不會傳送或請求任何資料。" + +#: src/Navigation.tsx:288 +#: src/view/screens/PreferencesExternalEmbeds.tsx:53 +#: src/view/screens/Settings/index.tsx:679 +msgid "External Media Preferences" +msgstr "外部媒體偏好" + +#: src/view/screens/Settings/index.tsx:670 +msgid "External media settings" +msgstr "外部媒體設定" + +#: src/view/com/modals/AddAppPasswords.tsx:119 +#: src/view/com/modals/AddAppPasswords.tsx:123 +msgid "Failed to create app password." +msgstr "建立應用程式專用密碼失敗。" + +#: src/view/com/modals/CreateOrEditList.tsx:194 +msgid "Failed to create the list. Check your internet connection and try again." +msgstr "無法建立列表。請檢查您的網路連線並重試。" + +#: src/components/dms/MessageMenu.tsx:73 +msgid "Failed to delete message" +msgstr "無法刪除訊息" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:152 +msgid "Failed to delete post, please try again" +msgstr "無法刪除貼文,請重試" + +#: src/view/screens/Search/Explore.tsx:417 +#: src/view/screens/Search/Explore.tsx:441 +msgid "Failed to load feeds preferences" +msgstr "無法載入動態源偏好" + +#: src/components/dialogs/GifSelect.ios.tsx:196 +#: src/components/dialogs/GifSelect.tsx:212 +msgid "Failed to load GIFs" +msgstr "無法載入 GIF" + +#: src/screens/Messages/Conversation/MessageListError.tsx:23 +msgid "Failed to load past messages" +msgstr "無法載入過去的訊息" + +#: src/view/screens/Search/Explore.tsx:410 +#: src/view/screens/Search/Explore.tsx:434 +msgid "Failed to load suggested feeds" +msgstr "無法載入建議的動態源" + +#: src/view/screens/Search/Explore.tsx:370 +msgid "Failed to load suggested follows" +msgstr "無法載入建議的跟隨者" + +#: src/view/com/lightbox/Lightbox.tsx:84 +msgid "Failed to save image: {0}" +msgstr "無法儲存圖片:{0}" + +#: src/components/dms/MessageItem.tsx:230 +msgid "Failed to send" +msgstr "無法傳送" + +#: src/components/moderation/LabelsOnMeDialog.tsx:223 +#: src/screens/Messages/Conversation/ChatDisabled.tsx:87 +msgid "Failed to submit appeal, please try again." +msgstr "無法提交申訴,請重試。" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:180 +msgid "Failed to toggle thread mute, please try again" +msgstr "無法將討論串設為靜音,請重試" + +#: src/components/FeedCard.tsx:160 +msgid "Failed to update feeds" +msgstr "無法更新動態" + +#: src/components/dms/MessagesNUX.tsx:60 +#: src/screens/Messages/Settings.tsx:35 +msgid "Failed to update settings" +msgstr "無法更新設定" + +#: src/Navigation.tsx:209 +msgid "Feed" +msgstr "動態" + +#: src/components/FeedCard.tsx:91 +#: src/view/com/feeds/FeedSourceCard.tsx:251 +msgid "Feed by {0}" +msgstr "{0} 建立的動態源" + +#: src/view/screens/Feeds.tsx:675 +msgid "Feed offline" +msgstr "動態源已離線" + +#: src/view/shell/desktop/RightNav.tsx:66 +#: src/view/shell/Drawer.tsx:345 +msgid "Feedback" +msgstr "意見回饋" + +#: src/view/screens/Feeds.tsx:433 +#: src/view/screens/Feeds.tsx:536 +#: src/view/screens/Profile.tsx:197 +#: src/view/screens/Search/Search.tsx:375 +#: src/view/shell/desktop/LeftNav.tsx:367 +#: src/view/shell/Drawer.tsx:493 +#: src/view/shell/Drawer.tsx:494 +msgid "Feeds" +msgstr "動態源" + +#: src/view/screens/SavedFeeds.tsx:180 +msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." +msgstr "動態源是一種自訂演算法,使用者只需掌握一點開發技巧即可輕鬆構建。更多資訊請<0/>。" + +#: src/components/FeedCard.tsx:157 +msgid "Feeds updated!" +msgstr "動態已更新!" + +#: src/view/com/modals/ChangeHandle.tsx:475 +msgid "File Contents" +msgstr "檔案內容" + +#: src/view/screens/Settings/ExportCarDialog.tsx:42 +msgid "File saved successfully!" +msgstr "文件儲存成功!" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:66 +msgid "Filter from feeds" +msgstr "動態源中的篩選" + +#: src/screens/Onboarding/StepFinished.tsx:184 +msgid "Finalizing" +msgstr "正在完成" + +#: src/view/com/posts/CustomFeedEmptyState.tsx:47 +#: src/view/com/posts/FollowingEmptyState.tsx:53 +#: src/view/com/posts/FollowingEndOfFeed.tsx:54 +msgid "Find accounts to follow" +msgstr "尋找一些帳號來跟隨" + +#: src/view/screens/Search/Search.tsx:439 +msgid "Find posts and users on Bluesky" +msgstr "在 Bluesky 上尋找貼文和用戶" + +#: src/view/screens/PreferencesFollowingFeed.tsx:110 +msgid "Fine-tune the content you see on your Following feed." +msgstr "對「Following」動態源中的內容進行微調,以下選項只對「Following」動態源起作用。" + +#: src/view/screens/PreferencesThreads.tsx:60 +msgid "Fine-tune the discussion threads." +msgstr "微調討論串。" + +#: src/screens/Onboarding/index.tsx:35 +msgid "Fitness" +msgstr "健康" + +#: src/screens/Onboarding/StepFinished.tsx:164 +msgid "Flexible" +msgstr "靈活" + +#: src/view/com/modals/EditImage.tsx:116 +msgid "Flip horizontal" +msgstr "水平翻轉" + +#: src/view/com/modals/EditImage.tsx:121 +#: src/view/com/modals/EditImage.tsx:288 +msgid "Flip vertically" +msgstr "垂直翻轉" + +#: src/components/ProfileHoverCard/index.web.tsx:446 +#: src/components/ProfileHoverCard/index.web.tsx:457 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:252 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:146 +#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:247 +msgid "Follow" +msgstr "跟隨" + +#: src/view/com/profile/FollowButton.tsx:69 +msgctxt "action" +msgid "Follow" +msgstr "跟隨" + +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:238 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:128 +msgid "Follow {0}" +msgstr "跟隨 {0}" + +#: src/view/com/posts/AviFollowButton.tsx:71 +msgid "Follow {name}" +msgstr "跟隨 {name}" + +#: src/view/com/profile/ProfileMenu.tsx:247 +#: src/view/com/profile/ProfileMenu.tsx:258 +msgid "Follow Account" +msgstr "跟隨帳號" + +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:144 +msgid "Follow Back" +msgstr "回追蹤" + +#: src/view/screens/Search/Explore.tsx:333 +msgid "Follow more accounts to get connected to your interests and build your network." +msgstr "跟隨更多帳號以瞭解您的興趣,並建立您的社群網路。" + +#: src/view/com/profile/ProfileCard.tsx:227 +msgid "Followed by {0}" +msgstr "由 {0} 跟隨" + +#: src/components/KnownFollowers.tsx:223 +msgid "Followed by <0>{0}" +msgstr "已被你跟隨的 <0>{0} 跟隨" + +#: src/components/KnownFollowers.tsx:209 +msgid "Followed by <0>{0} and {1, plural, one {# other} other {# others}}" +msgstr "已被你跟隨的 <0>{0} 和{1, plural, one {其他 # 人跟隨} other {其他 # 人跟}}" + +#: src/components/KnownFollowers.tsx:196 +msgid "Followed by <0>{0} and <1>{1}" +msgstr "已被你跟隨的 <0>{0} 和 <1>{1} 跟隨" + +#: src/components/KnownFollowers.tsx:178 +msgid "Followed by <0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}}" +msgstr "已被你跟隨的 <0>{0}, <1>{1} 和{2, plural, one {其他 # 人跟隨} other {其他 # 人跟隨}}" + +#: src/view/com/modals/Threadgate.tsx:101 +msgid "Followed users" +msgstr "已跟隨的用戶" + +#: src/view/screens/PreferencesFollowingFeed.tsx:153 +msgid "Followed users only" +msgstr "僅限已跟隨的用戶" + +#: src/view/com/notifications/FeedItem.tsx:175 +msgid "followed you" +msgstr "已跟隨您" + +#: src/view/com/profile/ProfileFollowers.tsx:104 +#: src/view/screens/ProfileFollowers.tsx:25 +msgid "Followers" +msgstr "跟隨者" + +#: src/Navigation.tsx:177 +msgid "Followers of @{0} that you know" +msgstr "您所認識的這些人也跟隨了 @{0}" + +#: src/screens/Profile/KnownFollowers.tsx:108 +#: src/screens/Profile/KnownFollowers.tsx:118 +msgid "Followers you know" +msgstr "您也認識的跟隨者" + +#: src/components/ProfileHoverCard/index.web.tsx:445 +#: src/components/ProfileHoverCard/index.web.tsx:456 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:250 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:149 +#: src/view/com/profile/ProfileFollows.tsx:104 +#: src/view/screens/Feeds.tsx:622 +#: src/view/screens/ProfileFollows.tsx:25 +#: src/view/screens/SavedFeeds.tsx:415 +msgid "Following" +msgstr "跟隨中" + +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:98 +msgid "Following {0}" +msgstr "已跟隨 {0}" + +#: src/view/com/posts/AviFollowButton.tsx:53 +msgid "Following {name}" +msgstr "已跟隨 {name}" + +#: src/view/screens/Settings/index.tsx:573 +msgid "Following feed preferences" +msgstr "「Following」動態源偏好" + +#: src/Navigation.tsx:275 +#: src/view/screens/PreferencesFollowingFeed.tsx:103 +#: src/view/screens/Settings/index.tsx:582 +msgid "Following Feed Preferences" +msgstr "「Following」動態源偏好" + +#: src/screens/Profile/Header/Handle.tsx:31 +msgid "Follows you" +msgstr "跟隨您" + +#: src/view/com/profile/ProfileCard.tsx:152 +msgid "Follows You" +msgstr "跟隨您" + +#: src/screens/Onboarding/index.tsx:40 +msgid "Food" +msgstr "食物" + +#: src/view/com/modals/DeleteAccount.tsx:129 +msgid "For security reasons, we'll need to send a confirmation code to your email address." +msgstr "為了保護您的帳號安全,我們需要將驗證碼發送到您的電子郵件地址。" + +#: src/view/com/modals/AddAppPasswords.tsx:232 +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 "為了保護您的帳號安全,您將無法再次查看此內容。如果您丟失了此密碼,您將需要再產生一個新的密碼。" + +#: src/screens/Login/index.tsx:129 +#: src/screens/Login/index.tsx:144 +msgid "Forgot Password" +msgstr "忘記密碼" + +#: src/screens/Login/LoginForm.tsx:224 +msgid "Forgot password?" +msgstr "忘記密碼?" + +#: src/screens/Login/LoginForm.tsx:235 +msgid "Forgot?" +msgstr "忘記?" + +#: src/lib/moderation/useReportOptions.ts:53 +msgid "Frequently Posts Unwanted Content" +msgstr "頻繁發佈不當內容" + +#: src/screens/Hashtag.tsx:118 +msgid "From @{sanitizedAuthor}" +msgstr "來自 @{sanitizedAuthor}" + +#: src/view/com/posts/FeedItem.tsx:236 +msgctxt "from-feed" +msgid "From <0/>" +msgstr "來自 <0/>" + +#: src/view/com/composer/photos/SelectPhotoBtn.tsx:39 +msgid "Gallery" +msgstr "相簿" + +#: src/components/dms/MessagesNUX.tsx:168 +msgid "Get started" +msgstr "開始" + +#: src/view/com/modals/VerifyEmail.tsx:197 +#: src/view/com/modals/VerifyEmail.tsx:199 +msgid "Get Started" +msgstr "開始" + +#: src/view/com/util/images/ImageHorzList.tsx:35 +msgid "GIF" +msgstr "GIF" + +#: src/screens/Onboarding/StepProfile/index.tsx:225 +msgid "Give your profile a face" +msgstr "為您的個人檔案增添新顏" + +#: src/lib/moderation/useReportOptions.ts:38 +msgid "Glaring violations of law or terms of service" +msgstr "明顯違反法律或服務條款" + +#: src/components/moderation/ScreenHider.tsx:151 +#: src/components/moderation/ScreenHider.tsx:160 +#: src/view/com/auth/LoggedOut.tsx:82 +#: src/view/com/auth/LoggedOut.tsx:83 +#: src/view/screens/NotFound.tsx:55 +#: src/view/screens/ProfileFeed.tsx:111 +#: src/view/screens/ProfileList.tsx:970 +#: src/view/shell/desktop/LeftNav.tsx:127 +msgid "Go back" +msgstr "返回" + +#: src/components/Error.tsx:103 +#: src/screens/Profile/ErrorState.tsx:62 +#: src/screens/Profile/ErrorState.tsx:66 +#: src/view/screens/NotFound.tsx:54 +#: src/view/screens/ProfileFeed.tsx:116 +#: src/view/screens/ProfileList.tsx:975 +msgid "Go Back" +msgstr "返回" + +#: src/components/dms/ReportDialog.tsx:154 +#: src/components/ReportDialog/SelectReportOptionView.tsx:77 +#: src/components/ReportDialog/SubmitView.tsx:104 +#: src/screens/Onboarding/Layout.tsx:102 +#: src/screens/Onboarding/Layout.tsx:191 +#: src/screens/Signup/index.tsx:187 +msgid "Go back to previous step" +msgstr "返回上一步" + +#: src/view/screens/NotFound.tsx:55 +msgid "Go home" +msgstr "前往首頁" + +#: src/view/screens/NotFound.tsx:54 +msgid "Go Home" +msgstr "前往首頁" + +#: src/screens/Messages/List/ChatListItem.tsx:211 +msgid "Go to conversation with {0}" +msgstr "與 {0} 對話" + +#: src/screens/Login/ForgotPasswordForm.tsx:172 +#: src/view/com/modals/ChangePassword.tsx:168 +msgid "Go to next" +msgstr "前往下一步" + +#: src/components/dms/ConvoMenu.tsx:167 +msgid "Go to profile" +msgstr "前往個人檔案" + +#: src/components/dms/ConvoMenu.tsx:164 +msgid "Go to user's profile" +msgstr "前往用戶的個人檔案" + +#: src/lib/moderation/useGlobalLabelStrings.ts:46 +msgid "Graphic Media" +msgstr "不適宜的圖像媒體" + +#: src/view/com/modals/ChangeHandle.tsx:260 +msgid "Handle" +msgstr "帳號代碼" + +#: src/view/screens/AccessibilitySettings.tsx:116 +msgid "Haptics" +msgstr "觸覺" + +#: src/lib/moderation/useReportOptions.ts:33 +msgid "Harassment, trolling, or intolerance" +msgstr "騷擾、惡作劇或其他無法容忍的行為" + +#: src/Navigation.tsx:303 +msgid "Hashtag" +msgstr "標籤" + +#: src/components/RichText.tsx:216 +msgid "Hashtag: #{tag}" +msgstr "標籤:#{tag}" + +#: src/screens/Signup/index.tsx:234 +msgid "Having trouble?" +msgstr "遇到問題?" + +#: src/view/shell/desktop/RightNav.tsx:95 +#: src/view/shell/Drawer.tsx:355 +msgid "Help" +msgstr "幫助" + +#: src/screens/Onboarding/StepProfile/index.tsx:228 +msgid "Help people know you're not a bot by uploading a picture or creating an avatar." +msgstr "透過上傳圖片或建立頭像來幫助人們知道您不是機器人。" + +#: src/view/com/modals/AddAppPasswords.tsx:203 +msgid "Here is your app password." +msgstr "這是您的應用程式專用密碼。" + +#: src/components/moderation/ContentHider.tsx:116 +#: src/components/moderation/LabelPreference.tsx:134 +#: src/components/moderation/PostHider.tsx:122 +#: src/lib/moderation/useLabelBehaviorDescription.ts:15 +#: src/lib/moderation/useLabelBehaviorDescription.ts:20 +#: src/lib/moderation/useLabelBehaviorDescription.ts:25 +#: src/lib/moderation/useLabelBehaviorDescription.ts:30 +#: src/view/com/util/forms/PostDropdownBtn.tsx:442 +msgid "Hide" +msgstr "隱藏" + +#: src/view/com/notifications/FeedItem.tsx:350 +msgctxt "action" +msgid "Hide" +msgstr "隱藏" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:387 +#: src/view/com/util/forms/PostDropdownBtn.tsx:389 +msgid "Hide post" +msgstr "隱藏貼文" + +#: src/components/moderation/ContentHider.tsx:68 +#: src/components/moderation/PostHider.tsx:79 +msgid "Hide the content" +msgstr "隱藏內容" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:439 +msgid "Hide this post?" +msgstr "隱藏這則貼文?" + +#: src/view/com/notifications/FeedItem.tsx:341 +msgid "Hide user list" +msgstr "隱藏用戶列表" + +#: src/view/com/posts/FeedErrorMessage.tsx:117 +msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." +msgstr "抱歉,與動態源的伺服器連線時發生了某種問題。請向該動態源的擁有者報告這個問題。" + +#: src/view/com/posts/FeedErrorMessage.tsx:105 +msgid "Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue." +msgstr "抱歉,動態源的伺服器似乎設定錯誤。請向該動態源的擁有者報告這個問題。" + +#: src/view/com/posts/FeedErrorMessage.tsx:111 +msgid "Hmm, the feed server appears to be offline. Please let the feed owner know about this issue." +msgstr "抱歉,動態源的伺服器似乎已離線。請向該動態源的擁有者報告這個問題。" + +#: src/view/com/posts/FeedErrorMessage.tsx:108 +msgid "Hmm, the feed server gave a bad response. Please let the feed owner know about this issue." +msgstr "抱歉,動態源的伺服器給出了錯誤的回應。請向該動態源的擁有者報告這個問題。" + +#: src/view/com/posts/FeedErrorMessage.tsx:102 +msgid "Hmm, we're having trouble finding this feed. It may have been deleted." +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 "抱歉,看起來我們在載入這些資料時遇到了問題,請參閱下方詳情。如果問題持續存在,請聯繫我們。" + +#: src/screens/Profile/ErrorState.tsx:31 +msgid "Hmmmm, we couldn't load that moderation service." +msgstr "抱歉,我們無法載入該內容管理服務。" + +#: src/Navigation.tsx:489 +#: src/view/shell/bottom-bar/BottomBar.tsx:159 +#: src/view/shell/desktop/LeftNav.tsx:335 +#: src/view/shell/Drawer.tsx:425 +#: src/view/shell/Drawer.tsx:426 +msgid "Home" +msgstr "首頁" + +#: src/view/com/modals/ChangeHandle.tsx:414 +msgid "Host:" +msgstr "主機:" + +#: src/screens/Login/ForgotPasswordForm.tsx:89 +#: src/screens/Login/LoginForm.tsx:157 +#: src/screens/Signup/StepInfo/index.tsx:40 +#: src/view/com/modals/ChangeHandle.tsx:275 +msgid "Hosting provider" +msgstr "託管服務供應商" + +#: src/view/com/modals/InAppBrowserConsent.tsx:44 +msgid "How should we open this link?" +msgstr "我們該如何開啟此連結?" + +#: src/view/com/modals/VerifyEmail.tsx:222 +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:132 +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:135 +msgid "I have a code" +msgstr "我有驗證碼" + +#: src/view/com/modals/VerifyEmail.tsx:224 +msgid "I have a confirmation code" +msgstr "我有驗證碼" + +#: src/view/com/modals/ChangeHandle.tsx:278 +msgid "I have my own domain" +msgstr "我擁有自己的網域" + +#: src/components/dms/BlockedByListDialog.tsx:56 +#: src/components/dms/ReportConversationPrompt.tsx:22 +msgid "I understand" +msgstr "我瞭解" + +#: src/view/com/lightbox/Lightbox.web.tsx:185 +msgid "If alt text is long, toggles alt text expanded state" +msgstr "替代文字過長時,切換替代文字的展開狀態" + +#: src/view/com/modals/SelfLabel.tsx:128 +msgid "If none are selected, suitable for all ages." +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 "如果根據您所在國家的法律,您尚未成年,則您的父母或法定監護人必須代表您閱讀這些條款。" + +#: src/view/screens/ProfileList.tsx:664 +msgid "If you delete this list, you won't be able to recover it." +msgstr "如果刪除這個列表,您將無法恢復它。" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:430 +msgid "If you remove this post, you won't be able to recover it." +msgstr "如果刪除這則貼文,您將無法恢復它。" + +#: src/view/com/modals/ChangePassword.tsx:149 +msgid "If you want to change your password, we will send you a code to verify that this is your account." +msgstr "如果您想更改密碼,我們將向您發送一個驗證碼以確認這是您的帳號。" + +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:92 +msgid "If you're trying to change your handle or email, do so before you deactivate." +msgstr "如果您想更改帳號代碼或電子郵件,請在停用帳號前更改。" + +#: src/lib/moderation/useReportOptions.ts:37 +msgid "Illegal and Urgent" +msgstr "違法" + +#: src/view/com/util/images/Gallery.tsx:42 +msgid "Image" +msgstr "圖片" + +#: src/view/com/modals/AltImage.tsx:122 +msgid "Image alt text" +msgstr "圖片替代文字" + +#: src/lib/moderation/useReportOptions.ts:48 +msgid "Impersonation or false claims about identity or affiliation" +msgstr "冒充或虛假聲明身份或隸屬關係" + +#: src/lib/moderation/useReportOptions.ts:85 +msgid "Inappropriate messages or explicit links" +msgstr "不當訊息或露骨連結" + +#: src/screens/Login/SetNewPasswordForm.tsx:127 +msgid "Input code sent to your email for password reset" +msgstr "輸入發送到您電子郵件地址的重設碼以重設密碼" + +#: src/view/com/modals/DeleteAccount.tsx:246 +msgid "Input confirmation code for account deletion" +msgstr "輸入刪除帳號的驗證碼" + +#: src/view/com/modals/AddAppPasswords.tsx:174 +msgid "Input name for app password" +msgstr "輸入應用程式專用密碼名稱" + +#: src/screens/Login/SetNewPasswordForm.tsx:151 +msgid "Input new password" +msgstr "輸入新密碼" + +#: src/view/com/modals/DeleteAccount.tsx:265 +msgid "Input password for account deletion" +msgstr "輸入密碼以刪除帳號" + +#: src/screens/Login/LoginForm.tsx:263 +msgid "Input the code which has been emailed to you" +msgstr "輸入寄送至您電子郵件地址的驗證碼" + +#: src/screens/Login/LoginForm.tsx:218 +msgid "Input the password tied to {identifier}" +msgstr "輸入與 {identifier} 關聯的密碼" + +#: src/screens/Login/LoginForm.tsx:191 +msgid "Input the username or email address you used at signup" +msgstr "輸入註冊時使用的用戶名稱或電子郵件地址" + +#: src/screens/Login/LoginForm.tsx:217 +msgid "Input your password" +msgstr "輸入您的密碼" + +#: src/view/com/modals/ChangeHandle.tsx:383 +msgid "Input your preferred hosting provider" +msgstr "輸入您的託管服務供應商" + +#: src/screens/Signup/StepHandle.tsx:63 +msgid "Input your user handle" +msgstr "輸入您的帳號代碼" + +#: src/components/dms/MessagesNUX.tsx:82 +msgid "Introducing Direct Messages" +msgstr "為您隆重介紹「私人訊息」" + +#: src/screens/Login/LoginForm.tsx:132 +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:70 +msgid "Invalid 2FA confirmation code." +msgstr "無效的雙重驗證碼。" + +#: src/view/com/post-thread/PostThreadItem.tsx:236 +msgid "Invalid or unsupported post record" +msgstr "無效或不支援的貼文紀錄" + +#: src/screens/Login/LoginForm.tsx:137 +msgid "Invalid username or password" +msgstr "用戶名稱或密碼無效" + +#: src/view/com/modals/InviteCodes.tsx:94 +msgid "Invite a Friend" +msgstr "邀請朋友" + +#: src/screens/Signup/StepInfo/index.tsx:58 +msgid "Invite code" +msgstr "邀請碼" + +#: src/screens/Signup/state.ts:275 +msgid "Invite code not accepted. Check that you input it correctly and try again." +msgstr "邀請碼無效。請檢查您輸入的內容是否正確,然後重試。" + +#: src/view/com/modals/InviteCodes.tsx:171 +msgid "Invite codes: {0} available" +msgstr "邀請碼:{0} 個可用" + +#: src/view/com/modals/InviteCodes.tsx:170 +msgid "Invite codes: 1 available" +msgstr "邀請碼:1 個可用" + +#: src/view/com/auth/SplashScreen.web.tsx:157 +msgid "Jobs" +msgstr "工作" + +#: src/screens/Onboarding/index.tsx:21 +msgid "Journalism" +msgstr "新聞學" + +#: src/components/moderation/ContentHider.tsx:147 +msgid "Labeled by {0}." +msgstr "由 {0} 標記。" + +#: src/components/moderation/ContentHider.tsx:145 +msgid "Labeled by the author." +msgstr "由作者標記。" + +#: src/view/screens/Profile.tsx:191 +msgid "Labels" +msgstr "標記" + +#: src/screens/Profile/Sections/Labels.tsx:163 +msgid "Labels are annotations on users and content. They can be used to hide, warn, and categorize the network." +msgstr "標記是對用戶和內容的標註,可用於隱藏、警告和對網路進行分類。" + +#: src/components/moderation/LabelsOnMeDialog.tsx:79 +msgid "Labels on your account" +msgstr "您帳號上的標記" + +#: src/components/moderation/LabelsOnMeDialog.tsx:81 +msgid "Labels on your content" +msgstr "您內容上的標記" + +#: src/view/com/composer/select-language/SelectLangBtn.tsx:105 +msgid "Language selection" +msgstr "語言選擇" + +#: src/view/screens/Settings/index.tsx:530 +msgid "Language settings" +msgstr "語言設定" + +#: src/Navigation.tsx:150 +#: src/view/screens/LanguageSettings.tsx:90 +msgid "Language Settings" +msgstr "語言設定" + +#: src/view/screens/Settings/index.tsx:539 +msgid "Languages" +msgstr "語言" + +#: src/screens/Hashtag.tsx:99 +#: src/view/screens/Search/Search.tsx:359 +msgid "Latest" +msgstr "最新" + +#: src/components/moderation/ScreenHider.tsx:136 +msgid "Learn More" +msgstr "瞭解詳情" + +#: src/components/moderation/ContentHider.tsx:66 +#: src/components/moderation/ContentHider.tsx:131 +msgid "Learn more about the moderation applied to this content." +msgstr "詳細瞭解套用於此內容的內容管理。" + +#: src/components/moderation/PostHider.tsx:100 +#: src/components/moderation/ScreenHider.tsx:125 +msgid "Learn more about this warning" +msgstr "瞭解有關此警告的更多資訊" + +#: src/screens/Moderation/index.tsx:549 +msgid "Learn more about what is public on Bluesky." +msgstr "瞭解有關 Bluesky 上公開內容的更多資訊。" + +#: src/components/moderation/ContentHider.tsx:155 +msgid "Learn more." +msgstr "瞭解詳情。" + +#: src/components/dms/LeaveConvoPrompt.tsx:50 +msgid "Leave" +msgstr "離開" + +#: src/components/dms/MessagesListBlockedFooter.tsx:66 +#: src/components/dms/MessagesListBlockedFooter.tsx:73 +msgid "Leave chat" +msgstr "離開對話" + +#: src/components/dms/ConvoMenu.tsx:138 +#: src/components/dms/ConvoMenu.tsx:141 +#: src/components/dms/ConvoMenu.tsx:208 +#: src/components/dms/ConvoMenu.tsx:211 +#: src/components/dms/LeaveConvoPrompt.tsx:46 +msgid "Leave conversation" +msgstr "離開對話" + +#: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:82 +msgid "Leave them all unchecked to see any language." +msgstr "全部留空以查看所有語言。" + +#: src/view/com/modals/LinkWarning.tsx:65 +msgid "Leaving Bluesky" +msgstr "離開 Bluesky" + +#: src/screens/SignupQueued.tsx:134 +msgid "left to go." +msgstr "個人在排在您前面。" + +#: src/view/screens/Settings/index.tsx:308 +msgid "Legacy storage cleared, you need to restart the app now." +msgstr "遺留資料已清除,您需要立即重新啟動應用程式。" + +#: src/screens/Login/index.tsx:130 +#: src/screens/Login/index.tsx:145 +msgid "Let's get your password reset!" +msgstr "讓我們來重設您的密碼吧!" + +#: src/screens/Onboarding/StepFinished.tsx:184 +msgid "Let's go!" +msgstr "讓我們開始吧!" + +#: src/view/screens/Settings/index.tsx:452 +msgid "Light" +msgstr "亮色" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:267 +#: src/view/screens/ProfileFeed.tsx:570 +msgid "Like this feed" +msgstr "對這個動態源按喜歡" + +#: src/components/LikesDialog.tsx:87 +#: src/Navigation.tsx:214 +#: src/Navigation.tsx:219 +msgid "Liked by" +msgstr "按喜歡的用戶" + +#: src/screens/Profile/ProfileLabelerLikedBy.tsx:29 +#: src/view/screens/PostLikedBy.tsx:27 +#: src/view/screens/ProfileFeedLikedBy.tsx:27 +msgid "Liked By" +msgstr "按喜歡的用戶" + +#: src/view/com/notifications/FeedItem.tsx:178 +msgid "liked your custom feed" +msgstr "對您的自訂動態源表示喜歡" + +#: src/view/com/notifications/FeedItem.tsx:170 +msgid "liked your post" +msgstr "已喜歡您的貼文" + +#: src/view/screens/Profile.tsx:196 +msgid "Likes" +msgstr "喜歡" + +#: src/view/com/post-thread/PostThreadItem.tsx:197 +msgid "Likes on this post" +msgstr "這條貼文的喜歡數" + +#: src/Navigation.tsx:183 +msgid "List" +msgstr "列表" + +#: src/view/com/modals/CreateOrEditList.tsx:250 +msgid "List Avatar" +msgstr "列表頭像" + +#: src/view/screens/ProfileList.tsx:358 +msgid "List blocked" +msgstr "列表已封鎖" + +#: src/view/com/feeds/FeedSourceCard.tsx:253 +msgid "List by {0}" +msgstr "列表由 {0} 建立" + +#: src/view/screens/ProfileList.tsx:397 +msgid "List deleted" +msgstr "列表已刪除" + +#: src/view/screens/ProfileList.tsx:330 +msgid "List muted" +msgstr "列表已靜音" + +#: src/view/com/modals/CreateOrEditList.tsx:264 +msgid "List Name" +msgstr "列表名稱" + +#: src/view/screens/ProfileList.tsx:372 +msgid "List unblocked" +msgstr "已解除封鎖的列表" + +#: src/view/screens/ProfileList.tsx:344 +msgid "List unmuted" +msgstr "已解除靜音的列表" + +#: src/Navigation.tsx:120 +#: src/view/screens/Profile.tsx:192 +#: src/view/screens/Profile.tsx:198 +#: src/view/shell/desktop/LeftNav.tsx:373 +#: src/view/shell/Drawer.tsx:509 +#: src/view/shell/Drawer.tsx:510 +msgid "Lists" +msgstr "列表" + +#: src/components/dms/BlockedByListDialog.tsx:39 +msgid "Lists blocking this user:" +msgstr "封鎖此用戶的列表:" + +#: src/view/screens/Search/Explore.tsx:130 +msgid "Load more" +msgstr "載入更多" + +#: src/view/screens/Search/Explore.tsx:218 +msgid "Load more suggested feeds" +msgstr "載入更多推薦動態" + +#: src/view/screens/Search/Explore.tsx:216 +msgid "Load more suggested follows" +msgstr "載入更多推薦跟隨者" + +#: src/view/screens/Notifications.tsx:184 +msgid "Load new notifications" +msgstr "載入新的通知" + +#: src/screens/Profile/Sections/Feed.tsx:86 +#: src/view/com/feeds/FeedPage.tsx:136 +#: src/view/screens/ProfileFeed.tsx:492 +#: src/view/screens/ProfileList.tsx:749 +msgid "Load new posts" +msgstr "載入新的貼文" + +#: src/view/com/composer/text-input/mobile/Autocomplete.tsx:99 +msgid "Loading..." +msgstr "載入中…" + +#: src/Navigation.tsx:234 +msgid "Log" +msgstr "日誌" + +#: src/screens/Deactivated.tsx:214 +#: src/screens/Deactivated.tsx:220 +msgid "Log in or sign up" +msgstr "登入或註冊" + +#: src/screens/SignupQueued.tsx:155 +#: src/screens/SignupQueued.tsx:158 +#: src/screens/SignupQueued.tsx:184 +#: src/screens/SignupQueued.tsx:187 +msgid "Log out" +msgstr "登出" + +#: src/screens/Moderation/index.tsx:442 +msgid "Logged-out visibility" +msgstr "登出可見性" + +#: src/components/AccountList.tsx:58 +msgid "Login to account that is not listed" +msgstr "登入未列出的帳號" + +#: src/components/RichText.tsx:217 +msgid "Long press to open tag menu for #{tag}" +msgstr "長按開啟 #{tag} 的標籤選單" + +#: src/screens/Login/SetNewPasswordForm.tsx:116 +msgid "Looks like XXXXX-XXXXX" +msgstr "看起來像是 XXXXX-XXXXX" + +#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:39 +msgid "Looks like you haven't saved any feeds! Use our recommendations or browse more below." +msgstr "您似乎尚未儲存任何動態源!參考我們的建議或瀏覽下面的更多內容。" + +#: src/screens/Home/NoFeedsPinned.tsx:83 +msgid "Looks like you unpinned all your feeds. But don't worry, you can add some below 😄" +msgstr "看起來您已取消釘選所有動態源。但不用擔心,您可以在下面新增一些😄" + +#: src/screens/Feeds/NoFollowingFeed.tsx:37 +msgid "Looks like you're missing a following feed. <0>Click here to add one." +msgstr "您看起來需要「Following」動態源,<0>點選這裡來新增。" + +#: src/view/com/modals/LinkWarning.tsx:79 +msgid "Make sure this is where you intend to go!" +msgstr "請確認這是您想要去的的地方!" + +#: src/components/dialogs/MutedWords.tsx:82 +msgid "Manage your muted words and tags" +msgstr "管理您靜音的文字和標籤" + +#: src/components/dms/ConvoMenu.tsx:151 +#: src/components/dms/ConvoMenu.tsx:158 +msgid "Mark as read" +msgstr "標記為已讀" + +#: src/view/screens/AccessibilitySettings.tsx:102 +#: src/view/screens/Profile.tsx:195 +msgid "Media" +msgstr "媒體" + +#: src/view/com/threadgate/WhoCanReply.tsx:270 +msgid "mentioned users" +msgstr "被提及的用戶" + +#: src/view/com/modals/Threadgate.tsx:96 +msgid "Mentioned users" +msgstr "被提及的用戶" + +#: src/view/com/util/ViewHeader.tsx:91 +#: src/view/screens/Search/Search.tsx:683 +msgid "Menu" +msgstr "選單" + +#: src/components/dms/MessageProfileButton.tsx:67 +msgid "Message {0}" +msgstr "給 {0} 傳送訊息" + +#: src/components/dms/MessageMenu.tsx:72 +#: src/screens/Messages/List/ChatListItem.tsx:155 +msgid "Message deleted" +msgstr "訊息已刪除" + +#: src/view/com/posts/FeedErrorMessage.tsx:200 +msgid "Message from server: {0}" +msgstr "來自伺服器的訊息:{0}" + +#: src/screens/Messages/Conversation/MessageInput.tsx:138 +msgid "Message input field" +msgstr "訊息輸入欄位" + +#: src/screens/Messages/Conversation/MessageInput.tsx:70 +#: src/screens/Messages/Conversation/MessageInput.web.tsx:49 +msgid "Message is too long" +msgstr "訊息太長了" + +#: src/screens/Messages/List/index.tsx:321 +msgid "Message settings" +msgstr "訊息設定" + +#: src/Navigation.tsx:504 +#: src/screens/Messages/List/index.tsx:164 +#: src/screens/Messages/List/index.tsx:246 +#: src/screens/Messages/List/index.tsx:317 +msgid "Messages" +msgstr "訊息" + +#: src/lib/moderation/useReportOptions.ts:46 +msgid "Misleading Account" +msgstr "誤導性帳號" + +#: src/Navigation.tsx:125 +#: src/screens/Moderation/index.tsx:104 +#: src/view/screens/Settings/index.tsx:561 +msgid "Moderation" +msgstr "內容管理" + +#: src/components/moderation/ModerationDetailsDialog.tsx:112 +msgid "Moderation details" +msgstr "內容管理詳情" + +#: src/view/com/lists/ListCard.tsx:95 +#: src/view/com/modals/UserAddRemoveLists.tsx:217 +msgid "Moderation list by {0}" +msgstr "由 {0} 建立的內容管理列表" + +#: src/view/screens/ProfileList.tsx:843 +msgid "Moderation list by <0/>" +msgstr "由 建立的內容管理列表" + +#: src/view/com/lists/ListCard.tsx:93 +#: src/view/com/modals/UserAddRemoveLists.tsx:215 +#: src/view/screens/ProfileList.tsx:841 +msgid "Moderation list by you" +msgstr "您建立的內容管理列表" + +#: src/view/com/modals/CreateOrEditList.tsx:185 +msgid "Moderation list created" +msgstr "已建立內容管理列表" + +#: src/view/com/modals/CreateOrEditList.tsx:171 +msgid "Moderation list updated" +msgstr "內容管理列表已更新" + +#: src/screens/Moderation/index.tsx:243 +msgid "Moderation lists" +msgstr "內容管理列表" + +#: src/Navigation.tsx:130 +#: src/view/screens/ModerationModlists.tsx:58 +msgid "Moderation Lists" +msgstr "內容管理列表" + +#: src/view/screens/Settings/index.tsx:555 +msgid "Moderation settings" +msgstr "內容管理設定" + +#: src/Navigation.tsx:229 +msgid "Moderation states" +msgstr "內容管理狀態" + +#: src/screens/Moderation/index.tsx:215 +msgid "Moderation tools" +msgstr "內容管理工具" + +#: src/components/moderation/ModerationDetailsDialog.tsx:48 +#: src/lib/moderation/useModerationCauseDescription.ts:42 +msgid "Moderator has chosen to set a general warning on the content." +msgstr "內容管理者已將此內容標記為普通警告。" + +#: src/view/com/post-thread/PostThreadItem.tsx:567 +msgid "More" +msgstr "更多" + +#: src/view/shell/desktop/Feeds.tsx:55 +msgid "More feeds" +msgstr "更多動態源" + +#: src/view/screens/ProfileList.tsx:653 +msgid "More options" +msgstr "更多選項" + +#: src/view/screens/PreferencesThreads.tsx:82 +msgid "Most-liked replies first" +msgstr "最多喜歡數優先" + +#: src/components/TagMenu/index.tsx:249 +msgid "Mute" +msgstr "靜音" + +#: src/components/TagMenu/index.web.tsx:105 +msgid "Mute {truncatedTag}" +msgstr "靜音 {truncatedTag}" + +#: src/view/com/profile/ProfileMenu.tsx:284 +#: src/view/com/profile/ProfileMenu.tsx:291 +msgid "Mute Account" +msgstr "靜音帳號" + +#: src/view/screens/ProfileList.tsx:572 +msgid "Mute accounts" +msgstr "靜音帳號" + +#: src/components/TagMenu/index.tsx:209 +msgid "Mute all {displayTag} posts" +msgstr "將所有 {displayTag} 貼文靜音" + +#: src/components/dms/ConvoMenu.tsx:172 +#: src/components/dms/ConvoMenu.tsx:178 +msgid "Mute conversation" +msgstr "靜音對話" + +#: src/components/dialogs/MutedWords.tsx:148 +msgid "Mute in tags only" +msgstr "僅靜音標籤" + +#: src/components/dialogs/MutedWords.tsx:133 +msgid "Mute in text & tags" +msgstr "靜音文字和標籤" + +#: src/view/screens/ProfileList.tsx:678 +msgid "Mute list" +msgstr "靜音列表" + +#: src/view/screens/ProfileList.tsx:673 +msgid "Mute these accounts?" +msgstr "靜音這些帳號?" + +#: src/components/dialogs/MutedWords.tsx:126 +msgid "Mute this word in post text and tags" +msgstr "在貼文內容和話題標籤中隱藏該文字" + +#: src/components/dialogs/MutedWords.tsx:141 +msgid "Mute this word in tags only" +msgstr "僅在話題標籤中隱藏該文字" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:362 +#: src/view/com/util/forms/PostDropdownBtn.tsx:368 +msgid "Mute thread" +msgstr "靜音討論串" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:378 +#: src/view/com/util/forms/PostDropdownBtn.tsx:380 +msgid "Mute words & tags" +msgstr "靜音文字和標籤" + +#: src/view/com/lists/ListCard.tsx:104 +msgid "Muted" +msgstr "已靜音" + +#: src/screens/Moderation/index.tsx:255 +msgid "Muted accounts" +msgstr "已靜音帳號" + +#: src/Navigation.tsx:135 +#: src/view/screens/ModerationMutedAccounts.tsx:109 +msgid "Muted Accounts" +msgstr "已靜音帳號" + +#: src/view/screens/ModerationMutedAccounts.tsx:117 +msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private." +msgstr "已靜音的帳號將不會在您的通知或動態中顯示,靜音資訊是完全非公開的。" + +#: src/lib/moderation/useModerationCauseDescription.ts:87 +msgid "Muted by \"{0}\"" +msgstr "被「{0}」靜音" + +#: src/screens/Moderation/index.tsx:231 +msgid "Muted words & tags" +msgstr "靜音文字和標籤" + +#: src/view/screens/ProfileList.tsx:675 +msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." +msgstr "封鎖是私人的。被封鎖的帳號可以與您互動,但您將無法看到他們的貼文或收到來自他們的通知。" + +#: src/components/dialogs/BirthDateSettings.tsx:35 +#: src/components/dialogs/BirthDateSettings.tsx:38 +msgid "My Birthday" +msgstr "我的生日" + +#: src/view/screens/Feeds.tsx:734 +msgid "My Feeds" +msgstr "我的動態源" + +#: src/view/shell/desktop/LeftNav.tsx:84 +msgid "My Profile" +msgstr "我的個人檔案" + +#: src/view/screens/Settings/index.tsx:616 +msgid "My saved feeds" +msgstr "我儲存的動態源" + +#: src/view/screens/Settings/index.tsx:622 +msgid "My Saved Feeds" +msgstr "我儲存的動態源" + +#: src/view/com/modals/AddAppPasswords.tsx:173 +#: src/view/com/modals/CreateOrEditList.tsx:279 +msgid "Name" +msgstr "名稱" + +#: src/view/com/modals/CreateOrEditList.tsx:143 +msgid "Name is required" +msgstr "名稱是必填項" + +#: src/lib/moderation/useReportOptions.ts:58 +#: src/lib/moderation/useReportOptions.ts:92 +#: src/lib/moderation/useReportOptions.ts:100 +msgid "Name or Description Violates Community Standards" +msgstr "名稱或描述違反社群標準" + +#: src/screens/Onboarding/index.tsx:22 +msgid "Nature" +msgstr "自然" + +#: src/screens/Login/ForgotPasswordForm.tsx:173 +#: src/screens/Login/LoginForm.tsx:309 +#: src/view/com/modals/ChangePassword.tsx:169 +msgid "Navigates to the next screen" +msgstr "切換到下一畫面" + +#: src/view/shell/Drawer.tsx:79 +msgid "Navigates to your profile" +msgstr "切換到您的個人檔案" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:127 +msgid "Need to report a copyright violation?" +msgstr "需要檢舉侵權嗎?" + +#: src/screens/Onboarding/StepFinished.tsx:152 +msgid "Never lose access to your followers or data." +msgstr "永遠不會失去對您的跟隨者或資料的存取權。" + +#: src/view/com/modals/ChangeHandle.tsx:515 +msgid "Nevermind, create a handle for me" +msgstr "不用了,為我建立一個帳號代碼" + +#: src/view/screens/Lists.tsx:81 +msgctxt "action" +msgid "New" +msgstr "新增" + +#: src/view/screens/ModerationModlists.tsx:78 +msgid "New" +msgstr "新增" + +#: src/components/dms/dialogs/NewChatDialog.tsx:52 +#: src/screens/Messages/List/index.tsx:331 +#: src/screens/Messages/List/index.tsx:338 +msgid "New chat" +msgstr "新對話" + +#: src/components/dms/NewMessagesPill.tsx:92 +msgid "New messages" +msgstr "新訊息" + +#: src/view/com/modals/CreateOrEditList.tsx:241 +msgid "New Moderation List" +msgstr "新的內容管理列表" + +#: src/view/com/modals/ChangePassword.tsx:213 +msgid "New password" +msgstr "新密碼" + +#: src/view/com/modals/ChangePassword.tsx:218 +msgid "New Password" +msgstr "新密碼" + +#: src/view/com/feeds/FeedPage.tsx:147 +msgctxt "action" +msgid "New post" +msgstr "新貼文" + +#: src/view/screens/Feeds.tsx:566 +#: src/view/screens/Notifications.tsx:193 +#: src/view/screens/Profile.tsx:464 +#: src/view/screens/ProfileFeed.tsx:426 +#: src/view/screens/ProfileList.tsx:201 +#: src/view/screens/ProfileList.tsx:229 +#: src/view/shell/desktop/LeftNav.tsx:271 +msgid "New post" +msgstr "新貼文" + +#: src/view/shell/desktop/LeftNav.tsx:277 +msgctxt "action" +msgid "New Post" +msgstr "新貼文" + +#: src/components/NewskieDialog.tsx:68 +msgid "New user info dialog" +msgstr "新用戶資訊對話框" + +#: src/view/com/modals/CreateOrEditList.tsx:236 +msgid "New User List" +msgstr "新的用戶列表" + +#: src/view/screens/PreferencesThreads.tsx:79 +msgid "Newest replies first" +msgstr "最新回覆優先" + +#: src/screens/Onboarding/index.tsx:20 +msgid "News" +msgstr "新聞" + +#: src/screens/Login/ForgotPasswordForm.tsx:143 +#: src/screens/Login/ForgotPasswordForm.tsx:150 +#: src/screens/Login/LoginForm.tsx:308 +#: src/screens/Login/LoginForm.tsx:315 +#: src/screens/Login/SetNewPasswordForm.tsx:174 +#: src/screens/Login/SetNewPasswordForm.tsx:180 +#: src/screens/Signup/index.tsx:220 +#: src/view/com/modals/ChangePassword.tsx:254 +#: src/view/com/modals/ChangePassword.tsx:256 +msgid "Next" +msgstr "下一個" + +#: src/view/com/lightbox/Lightbox.web.tsx:169 +msgid "Next image" +msgstr "下一張圖片" + +#: src/view/screens/PreferencesFollowingFeed.tsx:128 +#: src/view/screens/PreferencesFollowingFeed.tsx:199 +#: src/view/screens/PreferencesFollowingFeed.tsx:234 +#: src/view/screens/PreferencesFollowingFeed.tsx:271 +#: src/view/screens/PreferencesThreads.tsx:106 +#: src/view/screens/PreferencesThreads.tsx:129 +msgid "No" +msgstr "關" + +#: src/view/screens/ProfileFeed.tsx:559 +#: src/view/screens/ProfileList.tsx:823 +msgid "No description" +msgstr "沒有描述" + +#: src/view/com/modals/ChangeHandle.tsx:399 +msgid "No DNS Panel" +msgstr "無 DNS 控制台" + +#: src/components/dialogs/GifSelect.ios.tsx:202 +#: src/components/dialogs/GifSelect.tsx:218 +msgid "No featured GIFs found. There may be an issue with Tenor." +msgstr "未找到精選 GIF,Tenor 可能發生問題。" + +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:120 +msgid "No longer following {0}" +msgstr "不再跟隨 {0}" + +#: src/screens/Signup/StepHandle.tsx:115 +msgid "No longer than 253 characters" +msgstr "不超過 253 個字符" + +#: src/screens/Messages/List/ChatListItem.tsx:106 +msgid "No messages yet" +msgstr "還沒有訊息" + +#: src/screens/Messages/List/index.tsx:274 +msgid "No more conversations to show" +msgstr "已經沒有對話啦!" + +#: src/view/com/notifications/Feed.tsx:118 +msgid "No notifications yet!" +msgstr "還沒有通知!" + +#: src/components/dms/MessagesNUX.tsx:149 +#: src/components/dms/MessagesNUX.tsx:152 +#: src/screens/Messages/Settings.tsx:93 +#: src/screens/Messages/Settings.tsx:96 +msgid "No one" +msgstr "沒有人" + +#: src/screens/Profile/Sections/Feed.tsx:59 +msgid "No posts yet." +msgstr "目前還沒有貼文。" + +#: src/view/com/composer/text-input/mobile/Autocomplete.tsx:101 +#: src/view/com/composer/text-input/web/Autocomplete.tsx:195 +msgid "No result" +msgstr "沒有結果" + +#: src/components/dms/dialogs/SearchablePeopleList.tsx:202 +msgid "No results" +msgstr "沒有結果" + +#: src/components/Lists.tsx:207 +msgid "No results found" +msgstr "未找到結果" + +#: src/view/screens/Feeds.tsx:497 +msgid "No results found for \"{query}\"" +msgstr "未找到「{query}」的結果" + +#: src/view/com/modals/ListAddRemoveUsers.tsx:127 +#: src/view/screens/Search/Search.tsx:233 +#: src/view/screens/Search/Search.tsx:272 +#: src/view/screens/Search/Search.tsx:318 +msgid "No results found for {query}" +msgstr "未找到 {query} 的結果" + +#: src/components/dialogs/GifSelect.ios.tsx:200 +#: src/components/dialogs/GifSelect.tsx:216 +msgid "No search results found for \"{search}\"." +msgstr "未找到「{search}」的搜尋結果。" + +#: src/components/dialogs/EmbedConsent.tsx:105 +#: src/components/dialogs/EmbedConsent.tsx:112 +msgid "No thanks" +msgstr "不,謝謝" + +#: src/view/com/modals/Threadgate.tsx:85 +msgid "Nobody" +msgstr "沒有人" + +#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:48 +msgid "Nobody can reply" +msgstr "沒有人可以回覆" + +#: src/components/LikedByList.tsx:79 +#: src/components/LikesDialog.tsx:99 +msgid "Nobody has liked this yet. Maybe you should be the first!" +msgstr "還沒有人按喜歡,也許您應該成為第一個!" + +#: src/lib/moderation/useGlobalLabelStrings.ts:42 +msgid "Non-sexual Nudity" +msgstr "非色情內容裸體" + +#: src/Navigation.tsx:115 +#: src/view/screens/Profile.tsx:100 +msgid "Not Found" +msgstr "未找到" + +#: src/view/com/modals/VerifyEmail.tsx:254 +#: src/view/com/modals/VerifyEmail.tsx:260 +msgid "Not right now" +msgstr "暫時不需要" + +#: src/view/com/profile/ProfileMenu.tsx:373 +#: src/view/com/util/forms/PostDropdownBtn.tsx:456 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:311 +msgid "Note about sharing" +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." +msgstr "注意:Bluesky 是一個開放且公開的網路。此設定僅限制您在 Bluesky 應用程式和網站上的內容可見性,其他應用程式可能不會遵循這個規則。您的內容仍可能由其他應用程式和網站顯示給未登入的使用者。" + +#: src/screens/Messages/List/index.tsx:215 +msgid "Nothing here" +msgstr "這裡什麼也沒有" + +#: src/screens/Messages/Settings.tsx:124 +msgid "Notification sounds" +msgstr "通知音效" + +#: src/screens/Messages/Settings.tsx:121 +msgid "Notification Sounds" +msgstr "通知音效" + +#: src/Navigation.tsx:499 +#: src/view/screens/Notifications.tsx:132 +#: src/view/screens/Notifications.tsx:169 +#: src/view/shell/bottom-bar/BottomBar.tsx:227 +#: src/view/shell/desktop/LeftNav.tsx:350 +#: src/view/shell/Drawer.tsx:457 +#: src/view/shell/Drawer.tsx:458 +msgid "Notifications" +msgstr "通知" + +#: src/lib/hooks/useTimeAgo.ts:51 +msgid "now" +msgstr "現在" + +#: src/components/dms/MessageItem.tsx:175 +msgid "Now" +msgstr "現在" + +#: src/view/com/modals/SelfLabel.tsx:104 +msgid "Nudity" +msgstr "裸露" + +#: src/lib/moderation/useReportOptions.ts:72 +msgid "Nudity or adult content not labeled as such" +msgstr "未貼上此類標記的裸露或成人內容" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:11 +msgid "Off" +msgstr "顯示" + +#: src/components/dialogs/GifSelect.ios.tsx:237 +#: src/components/dialogs/GifSelect.tsx:255 +#: src/view/com/util/ErrorBoundary.tsx:55 +msgid "Oh no!" +msgstr "糟糕!" + +#: src/screens/Onboarding/StepInterests/index.tsx:133 +msgid "Oh no! Something went wrong." +msgstr "糟糕!發生了一些錯誤。" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:336 +msgid "OK" +msgstr "好的" + +#: src/screens/Login/PasswordUpdatedForm.tsx:44 +msgid "Okay" +msgstr "好的" + +#: src/view/screens/PreferencesThreads.tsx:78 +msgid "Oldest replies first" +msgstr "最舊的回覆優先" + +#: src/lib/hooks/useTimeAgo.ts:81 +msgid "on {str}" +msgstr "在 {str}" + +#: src/view/screens/Settings/index.tsx:256 +msgid "Onboarding reset" +msgstr "重新開始引導流程" + +#: src/view/com/composer/Composer.tsx:505 +msgid "One or more images is missing alt text." +msgstr "至少有一張圖片缺失了替代文字。" + +#: src/screens/Onboarding/StepProfile/index.tsx:117 +msgid "Only .jpg and .png files are supported" +msgstr "僅支援 .jpg 或 .png 格式的圖片" + +#: src/view/com/threadgate/WhoCanReply.tsx:239 +msgid "Only {0} can reply" +msgstr "只有{0}可以回覆" + +#: src/screens/Signup/StepHandle.tsx:98 +msgid "Only contains letters, numbers, and hyphens" +msgstr "只包含字母、數字和連字符" + +#: src/components/Lists.tsx:88 +msgid "Oops, something went wrong!" +msgstr "糟糕,發生了錯誤!" + +#: src/components/Lists.tsx:191 +#: src/view/screens/AppPasswords.tsx:69 +#: src/view/screens/Profile.tsx:100 +msgid "Oops!" +msgstr "糟糕!" + +#: src/screens/Onboarding/StepFinished.tsx:148 +msgid "Open" +msgstr "開啟" + +#: src/view/com/posts/AviFollowButton.tsx:89 +msgid "Open {name} profile shortcut menu" +msgstr "開啟 {name} 個人檔案快捷選單" + +#: src/screens/Onboarding/StepProfile/index.tsx:277 +msgid "Open avatar creator" +msgstr "開啟頭像建立工具" + +#: src/screens/Messages/List/ChatListItem.tsx:219 +#: src/screens/Messages/List/ChatListItem.tsx:220 +msgid "Open conversation options" +msgstr "開啟對話選項" + +#: src/view/com/composer/Composer.tsx:615 +#: src/view/com/composer/Composer.tsx:616 +msgid "Open emoji picker" +msgstr "開啟表情符號選擇器" + +#: src/view/screens/ProfileFeed.tsx:295 +msgid "Open feed options menu" +msgstr "開啟動態選項選單" + +#: src/view/screens/Settings/index.tsx:736 +msgid "Open links with in-app browser" +msgstr "在內建瀏覽器中開啟連結" + +#: src/components/dms/ActionsWrapper.tsx:87 +msgid "Open message options" +msgstr "開啟訊息選項" + +#: src/screens/Moderation/index.tsx:227 +msgid "Open muted words and tags settings" +msgstr "開啟靜音文字和標籤設定" + +#: src/view/com/home/HomeHeaderLayoutMobile.tsx:54 +msgid "Open navigation" +msgstr "開啟導覽" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:247 +msgid "Open post options menu" +msgstr "開啟貼文選項選單" + +#: src/view/screens/Settings/index.tsx:860 +#: src/view/screens/Settings/index.tsx:870 +msgid "Open storybook page" +msgstr "開啟故事書頁面" + +#: src/view/screens/Settings/index.tsx:848 +msgid "Open system log" +msgstr "開啟系統日誌" + +#: src/view/com/util/forms/DropdownButton.tsx:159 +msgid "Opens {numItems} options" +msgstr "開啟 {numItems} 個選項" + +#: src/view/screens/Settings/index.tsx:510 +msgid "Opens accessibility settings" +msgstr "開啟無障礙設定" + +#: src/view/screens/Log.tsx:58 +msgid "Opens additional details for a debug entry" +msgstr "開啟除錯項目的額外詳細資訊" + +#: src/view/com/composer/photos/OpenCameraBtn.tsx:74 +msgid "Opens camera on device" +msgstr "開啟裝置相機" + +#: src/view/screens/Settings/index.tsx:639 +msgid "Opens chat settings" +msgstr "開啟對話設定" + +#: src/view/com/composer/Prompt.tsx:27 +msgid "Opens composer" +msgstr "開啟編輯器" + +#: src/view/screens/Settings/index.tsx:531 +msgid "Opens configurable language settings" +msgstr "開啟可以更改的語言設定" + +#: src/view/com/composer/photos/SelectPhotoBtn.tsx:40 +msgid "Opens device photo gallery" +msgstr "開啟裝置相簿" + +#: src/view/screens/Settings/index.tsx:671 +msgid "Opens external embeds settings" +msgstr "開啟外部連結嵌入設定" + +#: src/view/com/auth/SplashScreen.tsx:50 +#: src/view/com/auth/SplashScreen.web.tsx:99 +msgid "Opens flow to create a new Bluesky account" +msgstr "開始建立新的 Bluesky 帳號的流程" + +#: src/view/com/auth/SplashScreen.tsx:65 +#: src/view/com/auth/SplashScreen.web.tsx:114 +msgid "Opens flow to sign into your existing Bluesky account" +msgstr "開始登入您現有的 Bluesky 帳號流程" + +#: src/view/com/composer/photos/SelectGifBtn.tsx:36 +msgid "Opens GIF select dialog" +msgstr "開啟 GIF 選擇對話框" + +#: src/view/com/modals/InviteCodes.tsx:173 +msgid "Opens list of invite codes" +msgstr "開啟邀請碼列表" + +#: src/view/screens/Settings/index.tsx:808 +msgid "Opens modal for account deactivation confirmation" +msgstr "開啟帳號刪除的確認彈窗" + +#: src/view/screens/Settings/index.tsx:830 +msgid "Opens modal for account deletion confirmation. Requires email code" +msgstr "開啟帳號刪除的確認彈窗。需要電子郵件驗證碼" + +#: src/view/screens/Settings/index.tsx:765 +msgid "Opens modal for changing your Bluesky password" +msgstr "開啟修改 Bluesky 密碼的彈窗" + +#: src/view/screens/Settings/index.tsx:720 +msgid "Opens modal for choosing a new Bluesky handle" +msgstr "開啟建立新 Bluesky 帳號代碼的彈窗" + +#: src/view/screens/Settings/index.tsx:788 +msgid "Opens modal for downloading your Bluesky account data (repository)" +msgstr "開啟下載 Bluesky 帳號數據(存儲庫)的彈窗" + +#: src/view/screens/Settings/index.tsx:1008 +msgid "Opens modal for email verification" +msgstr "開啟驗證電子郵件的彈窗" + +#: src/view/com/modals/ChangeHandle.tsx:276 +msgid "Opens modal for using custom domain" +msgstr "開啟使用自訂網域的彈窗" + +#: src/view/screens/Settings/index.tsx:556 +msgid "Opens moderation settings" +msgstr "開啟內容管理設定" + +#: src/screens/Login/LoginForm.tsx:225 +msgid "Opens password reset form" +msgstr "開啟密碼重設表單" + +#: src/view/screens/Settings/index.tsx:617 +msgid "Opens screen with all saved feeds" +msgstr "開啟包含所有已儲存的動態源之畫面" + +#: src/view/screens/Settings/index.tsx:698 +msgid "Opens the app password settings" +msgstr "開啟應用程式專用密碼設定畫面" + +#: src/view/screens/Settings/index.tsx:574 +msgid "Opens the Following feed preferences" +msgstr "開啟「Following」動態源偏好" + +#: src/view/com/modals/LinkWarning.tsx:93 +msgid "Opens the linked website" +msgstr "開啟網站連結" + +#: src/view/screens/Settings/index.tsx:861 +#: src/view/screens/Settings/index.tsx:871 +msgid "Opens the storybook page" +msgstr "開啟故事書頁面" + +#: src/view/screens/Settings/index.tsx:849 +msgid "Opens the system log page" +msgstr "開啟系統日誌頁面" + +#: src/view/screens/Settings/index.tsx:595 +msgid "Opens the threads preferences" +msgstr "開啟討論串偏好" + +#: src/view/com/notifications/FeedItem.tsx:429 +#: src/view/com/util/UserAvatar.tsx:422 +msgid "Opens this profile" +msgstr "開啟這個個人檔案" + +#: src/view/com/util/forms/DropdownButton.tsx:293 +msgid "Option {0} of {numItems}" +msgstr "{0} 選項,共 {numItems} 個" + +#: src/components/dms/ReportDialog.tsx:183 +#: src/components/ReportDialog/SubmitView.tsx:162 +msgid "Optionally provide additional information below:" +msgstr "在以下提供額外訊息(可選):" + +#: src/view/com/modals/Threadgate.tsx:92 +msgid "Or combine these options:" +msgstr "或者組合這些選項:" + +#: src/screens/Deactivated.tsx:211 +msgid "Or, continue with another account." +msgstr "或以其他帳號繼續。" + +#: src/screens/Deactivated.tsx:194 +msgid "Or, log into one of your other accounts." +msgstr "或登入您的其他帳號。" + +#: src/lib/moderation/useReportOptions.ts:26 +msgid "Other" +msgstr "其他" + +#: src/components/AccountList.tsx:76 +msgid "Other account" +msgstr "其他帳號" + +#: src/view/com/composer/select-language/SelectLangBtn.tsx:92 +msgid "Other..." +msgstr "其他…" + +#: src/screens/Messages/Conversation/ChatDisabled.tsx:28 +msgid "Our moderators have reviewed reports and decided to disable your access to chats on Bluesky." +msgstr "我們的內容管理者已審核檢舉,並決定停用您在 Bluesky 上的對話功能。" + +#: src/components/Lists.tsx:208 +#: src/view/screens/NotFound.tsx:45 +msgid "Page not found" +msgstr "頁面不存在" + +#: src/view/screens/NotFound.tsx:42 +msgid "Page Not Found" +msgstr "頁面不存在" + +#: src/screens/Login/LoginForm.tsx:201 +#: src/screens/Signup/StepInfo/index.tsx:102 +#: src/view/com/modals/DeleteAccount.tsx:257 +#: src/view/com/modals/DeleteAccount.tsx:264 +msgid "Password" +msgstr "密碼" + +#: src/view/com/modals/ChangePassword.tsx:143 +msgid "Password Changed" +msgstr "密碼已更改" + +#: src/screens/Login/index.tsx:157 +msgid "Password updated" +msgstr "密碼已更新" + +#: src/screens/Login/PasswordUpdatedForm.tsx:30 +msgid "Password updated!" +msgstr "密碼已更新!" + +#: src/view/com/util/post-embeds/GifEmbed.tsx:37 +msgid "Pause" +msgstr "暫停" + +#: src/view/screens/Search/Search.tsx:369 +msgid "People" +msgstr "用戶" + +#: src/Navigation.tsx:170 +msgid "People followed by @{0}" +msgstr "被 @{0} 跟隨的人" + +#: src/Navigation.tsx:163 +msgid "People following @{0}" +msgstr "跟隨 @{0} 的人" + +#: src/view/com/lightbox/Lightbox.tsx:67 +msgid "Permission to access camera roll is required." +msgstr "需要相簿權限。" + +#: src/view/com/lightbox/Lightbox.tsx:73 +msgid "Permission to access camera roll was denied. Please enable it in your system settings." +msgstr "相簿權限已遭拒絕,請在系統設定中啟用。" + +#: src/screens/Onboarding/index.tsx:28 +msgid "Pets" +msgstr "寵物" + +#: src/view/com/modals/SelfLabel.tsx:122 +msgid "Pictures meant for adults." +msgstr "適合成年人的圖像。" + +#: src/view/screens/ProfileFeed.tsx:287 +#: src/view/screens/ProfileList.tsx:617 +msgid "Pin to home" +msgstr "釘選到首頁" + +#: src/view/screens/ProfileFeed.tsx:290 +msgid "Pin to Home" +msgstr "釘選到首頁" + +#: src/view/screens/SavedFeeds.tsx:103 +msgid "Pinned Feeds" +msgstr "釘選的動態源列表" + +#: src/view/screens/ProfileList.tsx:289 +msgid "Pinned to your feeds" +msgstr "從您的動態中取消釘選" + +#: src/view/com/util/post-embeds/GifEmbed.tsx:37 +msgid "Play" +msgstr "播放" + +#: src/view/com/util/post-embeds/ExternalGifEmbed.tsx:123 +msgid "Play {0}" +msgstr "播放 {0}" + +#: src/view/com/util/post-embeds/GifEmbed.tsx:36 +msgid "Play or pause the GIF" +msgstr "播放或暫停 GIF" + +#: src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx:57 +#: src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx:58 +msgid "Play Video" +msgstr "播放影片" + +#: src/view/com/util/post-embeds/ExternalGifEmbed.tsx:122 +msgid "Plays the GIF" +msgstr "播放 GIF" + +#: src/screens/Signup/state.ts:234 +msgid "Please choose your handle." +msgstr "請設定您的帳號代碼。" + +#: src/screens/Signup/state.ts:227 +msgid "Please choose your password." +msgstr "請設定您的密碼。" + +#: src/screens/Signup/state.ts:248 +msgid "Please complete the verification captcha." +msgstr "請完成 Captcha 驗證。" + +#: src/view/com/modals/ChangeEmail.tsx:65 +msgid "Please confirm your email before changing it. This is a temporary requirement while email-updating tools are added, and it will soon be removed." +msgstr "更改前請先確認您的電子郵件地址。這是電子郵件更新工具的臨時要求,此限制很快就會被移除。" + +#: src/view/com/modals/AddAppPasswords.tsx:94 +msgid "Please enter a name for your app password. All spaces is not allowed." +msgstr "請輸入應用程式專用密碼的名稱。不允許包含任何空格。" + +#: src/view/com/modals/AddAppPasswords.tsx:150 +msgid "Please enter a unique name for this App Password or use our randomly generated one." +msgstr "請輸入此應用程式專用密碼的唯一名稱,或使用我們提供的隨機生成名稱。" + +#: src/components/dialogs/MutedWords.tsx:67 +msgid "Please enter a valid word, tag, or phrase to mute" +msgstr "請輸入有效的文字或標籤進行靜音" + +#: src/screens/Signup/state.ts:213 +msgid "Please enter your email." +msgstr "請輸入您的電子郵件。" + +#: src/view/com/modals/DeleteAccount.tsx:253 +msgid "Please enter your password as well:" +msgstr "請輸入您的密碼:" + +#: src/components/moderation/LabelsOnMeDialog.tsx:256 +msgid "Please explain why you think this label was incorrectly applied by {0}" +msgstr "請解釋您認為 {0} 不該套用此標記的原因" + +#: src/screens/Messages/Conversation/ChatDisabled.tsx:110 +msgid "Please explain why you think your chats were incorrectly disabled" +msgstr "請解釋您認為我們不該停用您對話功能的原因" + +#: src/lib/hooks/useAccountSwitcher.ts:48 +#: src/lib/hooks/useAccountSwitcher.ts:58 +msgid "Please sign in as @{0}" +msgstr "請以 @{0} 的身分登入" + +#: src/view/com/modals/VerifyEmail.tsx:109 +msgid "Please Verify Your Email" +msgstr "請驗證您的電子郵件地址" + +#: src/view/com/composer/Composer.tsx:281 +msgid "Please wait for your link card to finish loading" +msgstr "請等待您的連結預覽載入完畢" + +#: src/screens/Onboarding/index.tsx:34 +msgid "Politics" +msgstr "政治" + +#: src/view/com/modals/SelfLabel.tsx:112 +msgid "Porn" +msgstr "色情內容" + +#: src/view/com/composer/Composer.tsx:479 +#: src/view/com/composer/Composer.tsx:487 +msgctxt "action" +msgid "Post" +msgstr "發佈" + +#: src/view/com/post-thread/PostThread.tsx:434 +msgctxt "description" +msgid "Post" +msgstr "貼文" + +#: src/view/com/post-thread/PostThreadItem.tsx:189 +msgid "Post by {0}" +msgstr "{0} 的貼文" + +#: src/Navigation.tsx:189 +#: src/Navigation.tsx:196 +#: src/Navigation.tsx:203 +msgid "Post by @{0}" +msgstr "@{0} 的貼文" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:132 +msgid "Post deleted" +msgstr "貼文已刪除" + +#: src/view/com/post-thread/PostThread.tsx:193 +msgid "Post hidden" +msgstr "貼文已隱藏" + +#: src/components/moderation/ModerationDetailsDialog.tsx:97 +#: src/lib/moderation/useModerationCauseDescription.ts:101 +msgid "Post Hidden by Muted Word" +msgstr "貼文因靜音文字而被隱藏" + +#: src/components/moderation/ModerationDetailsDialog.tsx:100 +#: src/lib/moderation/useModerationCauseDescription.ts:110 +msgid "Post Hidden by You" +msgstr "被您靜音的貼文" + +#: src/view/com/composer/select-language/SelectLangBtn.tsx:88 +msgid "Post language" +msgstr "貼文語言" + +#: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:75 +msgid "Post Languages" +msgstr "貼文語言" + +#: src/view/com/post-thread/PostThread.tsx:188 +#: src/view/com/post-thread/PostThread.tsx:200 +msgid "Post not found" +msgstr "找不到貼文" + +#: src/components/TagMenu/index.tsx:253 +msgid "posts" +msgstr "貼文" + +#: src/view/screens/Profile.tsx:193 +msgid "Posts" +msgstr "貼文" + +#: src/components/dialogs/MutedWords.tsx:89 +msgid "Posts can be muted based on their text, their tags, or both." +msgstr "可以靜音貼文所包含的文字和標籤。" + +#: src/view/com/posts/FeedErrorMessage.tsx:68 +msgid "Posts hidden" +msgstr "貼文已隱藏" + +#: src/view/com/modals/LinkWarning.tsx:60 +msgid "Potentially Misleading Link" +msgstr "潛在誤導性連結" + +#: src/screens/Messages/Conversation/MessageListError.tsx:19 +msgid "Press to attempt reconnection" +msgstr "點擊以重試連線" + +#: src/components/forms/HostingProvider.tsx:46 +msgid "Press to change hosting provider" +msgstr "按下以更改託管服務供應商" + +#: src/components/Error.tsx:85 +#: src/components/Lists.tsx:93 +#: src/screens/Messages/Conversation/MessageListError.tsx:24 +#: src/screens/Signup/index.tsx:200 +msgid "Press to retry" +msgstr "按下以重試" + +#: src/components/KnownFollowers.tsx:116 +msgid "Press to view followers of this account that you also follow" +msgstr "按下以查看哪些您認識的人跟隨了此帳號" + +#: src/view/com/lightbox/Lightbox.web.tsx:150 +msgid "Previous image" +msgstr "上一張圖片" + +#: src/view/screens/LanguageSettings.tsx:189 +msgid "Primary Language" +msgstr "主要語言" + +#: src/view/screens/PreferencesThreads.tsx:97 +msgid "Prioritize Your Follows" +msgstr "優先顯示跟隨者" + +#: src/view/screens/Settings/index.tsx:654 +#: src/view/shell/desktop/RightNav.tsx:77 +msgid "Privacy" +msgstr "隱私" + +#: src/Navigation.tsx:244 +#: src/screens/Signup/StepInfo/Policies.tsx:56 +#: src/view/screens/PrivacyPolicy.tsx:29 +#: src/view/screens/Settings/index.tsx:957 +#: src/view/shell/Drawer.tsx:285 +msgid "Privacy Policy" +msgstr "隱私政策" + +#: src/components/dms/MessagesNUX.tsx:91 +msgid "Privately chat with other users." +msgstr "和其他用戶進行私人對話。" + +#: src/screens/Login/ForgotPasswordForm.tsx:156 +msgid "Processing..." +msgstr "處理中…" + +#: src/view/screens/DebugMod.tsx:894 +#: src/view/screens/Profile.tsx:345 +msgid "profile" +msgstr "個人檔案" + +#: src/view/shell/bottom-bar/BottomBar.tsx:272 +#: src/view/shell/desktop/LeftNav.tsx:381 +#: src/view/shell/Drawer.tsx:78 +#: src/view/shell/Drawer.tsx:542 +#: src/view/shell/Drawer.tsx:543 +msgid "Profile" +msgstr "個人檔案" + +#: src/view/com/modals/EditProfile.tsx:129 +msgid "Profile updated" +msgstr "個人檔案已更新" + +#: src/view/screens/Settings/index.tsx:1021 +msgid "Protect your account by verifying your email." +msgstr "通過驗證電子郵件地址來保護您的帳號。" + +#: src/screens/Onboarding/StepFinished.tsx:134 +msgid "Public" +msgstr "公開內容" + +#: src/view/screens/ModerationModlists.tsx:61 +msgid "Public, shareable lists of users to mute or block in bulk." +msgstr "公開且可共享的批量靜音或封鎖列表。" + +#: src/view/screens/Lists.tsx:66 +msgid "Public, shareable lists which can drive feeds." +msgstr "公開且可共享的列表,可作為動態源使用。" + +#: src/view/com/composer/Composer.tsx:464 +msgid "Publish post" +msgstr "發佈貼文" + +#: src/view/com/composer/Composer.tsx:464 +msgid "Publish reply" +msgstr "發佈回覆" + +#: src/view/com/util/post-ctrls/RepostButton.tsx:116 +#: src/view/com/util/post-ctrls/RepostButton.tsx:128 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:79 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:82 +msgid "Quote post" +msgstr "引用貼文" + +#: src/view/screens/PreferencesThreads.tsx:86 +msgid "Random (aka \"Poster's Roulette\")" +msgstr "隨機顯示 (又名試試手氣)" + +#: src/view/com/modals/EditImage.tsx:237 +msgid "Ratios" +msgstr "比率" + +#: src/screens/Deactivated.tsx:144 +msgid "Reactivate your account" +msgstr "重新啟用您的帳號" + +#: src/components/dms/ReportDialog.tsx:174 +msgid "Reason:" +msgstr "原因:" + +#: src/view/screens/Search/Search.tsx:933 +msgid "Recent Searches" +msgstr "最近的搜尋結果" + +#: src/screens/Messages/Conversation/MessageListError.tsx:20 +msgid "Reconnect" +msgstr "重新連線" + +#: src/screens/Messages/List/index.tsx:200 +msgid "Reload conversations" +msgstr "重新載入對話" + +#: src/components/dialogs/MutedWords.tsx:286 +#: src/components/FeedCard.tsx:200 +#: src/view/com/feeds/FeedSourceCard.tsx:317 +#: src/view/com/modals/ListAddRemoveUsers.tsx:268 +#: src/view/com/modals/SelfLabel.tsx:84 +#: src/view/com/modals/UserAddRemoveLists.tsx:230 +#: src/view/com/posts/FeedErrorMessage.tsx:212 +msgid "Remove" +msgstr "刪除" + +#: src/view/com/util/AccountDropdownBtn.tsx:22 +msgid "Remove account" +msgstr "刪除帳號" + +#: src/view/com/util/UserAvatar.tsx:384 +msgid "Remove Avatar" +msgstr "刪除頭像" + +#: src/view/com/util/UserBanner.tsx:155 +msgid "Remove Banner" +msgstr "刪除橫幅" + +#: src/screens/Messages/Conversation/MessageInputEmbed.tsx:218 +msgid "Remove embed" +msgstr "刪除嵌入" + +#: src/view/com/posts/FeedErrorMessage.tsx:168 +#: src/view/com/posts/FeedShutdownMsg.tsx:113 +#: src/view/com/posts/FeedShutdownMsg.tsx:117 +msgid "Remove feed" +msgstr "刪除動態源" + +#: src/view/com/posts/FeedErrorMessage.tsx:209 +msgid "Remove feed?" +msgstr "刪除動態源?" + +#: src/view/com/feeds/FeedSourceCard.tsx:188 +#: src/view/com/feeds/FeedSourceCard.tsx:266 +#: src/view/screens/ProfileFeed.tsx:330 +#: src/view/screens/ProfileFeed.tsx:336 +#: src/view/screens/ProfileList.tsx:443 +msgid "Remove from my feeds" +msgstr "從我的動態源中刪除" + +#: src/components/FeedCard.tsx:195 +#: src/view/com/feeds/FeedSourceCard.tsx:312 +msgid "Remove from my feeds?" +msgstr "從我的動態源中刪除?" + +#: src/view/com/composer/photos/Gallery.tsx:174 +msgid "Remove image" +msgstr "刪除圖片" + +#: src/view/com/composer/ExternalEmbed.tsx:87 +msgid "Remove image preview" +msgstr "刪除圖片預覽" + +#: src/components/dialogs/MutedWords.tsx:329 +msgid "Remove mute word from your list" +msgstr "從您的列表中刪除靜音文字" + +#: src/view/screens/Search/Search.tsx:974 +msgid "Remove profile" +msgstr "刪除個人檔案" + +#: src/view/screens/Search/Search.tsx:976 +msgid "Remove profile from search history" +msgstr "刪除搜尋紀錄中的個人檔案" + +#: src/view/com/util/post-embeds/QuoteEmbed.tsx:238 +msgid "Remove quote" +msgstr "刪除引用貼文" + +#: src/view/com/util/post-ctrls/RepostButton.tsx:93 +#: src/view/com/util/post-ctrls/RepostButton.tsx:109 +msgid "Remove repost" +msgstr "刪除轉貼貼文" + +#: src/view/com/posts/FeedErrorMessage.tsx:210 +msgid "Remove this feed from your saved feeds" +msgstr "將這個動態源從您已儲存之動態源列表中刪除" + +#: src/view/com/modals/ListAddRemoveUsers.tsx:199 +#: src/view/com/modals/UserAddRemoveLists.tsx:165 +msgid "Removed from list" +msgstr "從列表中刪除" + +#: src/view/com/feeds/FeedSourceCard.tsx:139 +msgid "Removed from my feeds" +msgstr "已從我的動態源中刪除" + +#: src/view/com/posts/FeedShutdownMsg.tsx:44 +#: src/view/screens/ProfileFeed.tsx:191 +#: src/view/screens/ProfileList.tsx:320 +msgid "Removed from your feeds" +msgstr "從您的動態中刪除" + +#: src/view/com/composer/ExternalEmbed.tsx:88 +msgid "Removes default thumbnail from {0}" +msgstr "從 {0} 中刪除預設縮圖" + +#: src/view/com/util/post-embeds/QuoteEmbed.tsx:239 +msgid "Removes quoted post" +msgstr "刪除已轉貼貼文" + +#: src/view/com/posts/FeedShutdownMsg.tsx:126 +#: src/view/com/posts/FeedShutdownMsg.tsx:130 +msgid "Replace with Discover" +msgstr "用「Discover」動態源取代" + +#: src/view/screens/Profile.tsx:194 +msgid "Replies" +msgstr "回覆" + +#: src/view/com/threadgate/WhoCanReply.tsx:66 +msgid "Replies disabled" +msgstr "回覆已被停用" + +#: src/view/com/threadgate/WhoCanReply.tsx:123 +msgid "Replies on this thread are disabled" +msgstr "此討論串的回覆已停用" + +#: src/view/com/threadgate/WhoCanReply.tsx:237 +msgid "Replies to this thread are disabled" +msgstr "此討論串的回覆已停用。" + +#: src/view/com/composer/Composer.tsx:477 +msgctxt "action" +msgid "Reply" +msgstr "回覆" + +#: src/view/screens/PreferencesFollowingFeed.tsx:143 +msgid "Reply Filters" +msgstr "回覆過濾器" + +#: src/view/com/post/Post.tsx:190 +#: src/view/com/posts/FeedItem.tsx:439 +msgctxt "description" +msgid "Reply to <0><1/>" +msgstr "對 <0><1/> 回覆" + +#: src/view/com/posts/FeedItem.tsx:437 +msgctxt "description" +msgid "Reply to a blocked post" +msgstr "對已被封鎖的貼文回覆" + +#: src/components/dms/MessageMenu.tsx:132 +#: src/components/dms/MessagesListBlockedFooter.tsx:77 +#: src/components/dms/MessagesListBlockedFooter.tsx:84 +msgid "Report" +msgstr "檢舉" + +#: src/view/com/profile/ProfileMenu.tsx:324 +#: src/view/com/profile/ProfileMenu.tsx:327 +msgid "Report Account" +msgstr "檢舉帳號" + +#: src/components/dms/ConvoMenu.tsx:197 +#: src/components/dms/ConvoMenu.tsx:200 +#: src/components/dms/ReportConversationPrompt.tsx:18 +msgid "Report conversation" +msgstr "檢舉對話" + +#: src/components/ReportDialog/index.tsx:49 +msgid "Report dialog" +msgstr "檢舉對話框" + +#: src/view/screens/ProfileFeed.tsx:347 +#: src/view/screens/ProfileFeed.tsx:349 +msgid "Report feed" +msgstr "檢舉動態源" + +#: src/view/screens/ProfileList.tsx:485 +msgid "Report List" +msgstr "檢舉列表" + +#: src/components/dms/MessageMenu.tsx:130 +msgid "Report message" +msgstr "檢舉訊息" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:404 +#: src/view/com/util/forms/PostDropdownBtn.tsx:406 +msgid "Report post" +msgstr "檢舉貼文" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:43 +msgid "Report this content" +msgstr "檢舉這個內容" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:56 +msgid "Report this feed" +msgstr "檢舉這個動態源" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:53 +msgid "Report this list" +msgstr "檢舉這個列表" + +#: src/components/dms/ReportDialog.tsx:48 +#: src/components/dms/ReportDialog.tsx:142 +#: src/components/ReportDialog/SelectReportOptionView.tsx:59 +msgid "Report this message" +msgstr "檢舉這個訊息" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:50 +msgid "Report this post" +msgstr "檢舉這則貼文" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:47 +msgid "Report this user" +msgstr "檢舉這個用戶" + +#: src/view/com/util/post-ctrls/RepostButton.tsx:65 +#: src/view/com/util/post-ctrls/RepostButton.tsx:94 +#: src/view/com/util/post-ctrls/RepostButton.tsx:110 +msgctxt "action" +msgid "Repost" +msgstr "轉貼" + +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:70 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:74 +msgid "Repost" +msgstr "轉貼" + +#: src/view/com/util/post-ctrls/RepostButton.tsx:86 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:47 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:93 +msgid "Repost or quote post" +msgstr "轉貼或引用貼文" + +#: src/view/screens/PostRepostedBy.tsx:27 +msgid "Reposted By" +msgstr "轉貼" + +#: src/view/com/posts/FeedItem.tsx:254 +msgid "Reposted by {0}" +msgstr "由 {0} 轉貼" + +#: src/view/com/posts/FeedItem.tsx:269 +msgid "Reposted by <0><1/>" +msgstr "由 <0><1/> 轉貼" + +#: src/view/com/notifications/FeedItem.tsx:172 +msgid "reposted your post" +msgstr "轉貼您的貼文" + +#: src/view/com/post-thread/PostThreadItem.tsx:202 +msgid "Reposts of this post" +msgstr "轉貼這則貼文" + +#: src/view/com/modals/ChangeEmail.tsx:176 +#: src/view/com/modals/ChangeEmail.tsx:178 +msgid "Request Change" +msgstr "請求變更" + +#: src/view/com/modals/ChangePassword.tsx:242 +#: src/view/com/modals/ChangePassword.tsx:244 +msgid "Request Code" +msgstr "請求代碼" + +#: src/view/screens/AccessibilitySettings.tsx:88 +msgid "Require alt text before posting" +msgstr "要求發佈前提供替代文字" + +#: src/view/screens/Settings/Email2FAToggle.tsx:51 +msgid "Require email code to log into your account" +msgstr "登入時要求電子郵件驗證碼" + +#: src/screens/Signup/StepInfo/index.tsx:69 +msgid "Required for this provider" +msgstr "此供應商要求必填" + +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:168 +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:171 +msgid "Resend email" +msgstr "重新傳送郵件" + +#: src/view/com/modals/ChangePassword.tsx:186 +msgid "Reset code" +msgstr "重設碼" + +#: src/view/com/modals/ChangePassword.tsx:193 +msgid "Reset Code" +msgstr "重設碼" + +#: src/view/screens/Settings/index.tsx:900 +#: src/view/screens/Settings/index.tsx:903 +msgid "Reset onboarding state" +msgstr "重設初始設定進行狀態" + +#: src/screens/Login/ForgotPasswordForm.tsx:86 +msgid "Reset password" +msgstr "重設密碼" + +#: src/view/screens/Settings/index.tsx:880 +#: src/view/screens/Settings/index.tsx:883 +msgid "Reset preferences state" +msgstr "重設偏好狀態" + +#: src/view/screens/Settings/index.tsx:901 +msgid "Resets the onboarding state" +msgstr "重設初始設定狀態" + +#: src/view/screens/Settings/index.tsx:881 +msgid "Resets the preferences state" +msgstr "重設偏好狀態" + +#: src/screens/Login/LoginForm.tsx:289 +msgid "Retries login" +msgstr "重試登入" + +#: src/view/com/util/error/ErrorMessage.tsx:57 +#: src/view/com/util/error/ErrorScreen.tsx:74 +msgid "Retries the last action, which errored out" +msgstr "重試上次出錯的操作" + +#: src/components/dms/MessageItem.tsx:241 +#: src/components/Error.tsx:90 +#: src/components/Lists.tsx:104 +#: src/screens/Login/LoginForm.tsx:288 +#: src/screens/Login/LoginForm.tsx:295 +#: src/screens/Messages/Conversation/MessageListError.tsx:25 +#: src/screens/Onboarding/StepInterests/index.tsx:226 +#: src/screens/Onboarding/StepInterests/index.tsx:229 +#: src/screens/Signup/index.tsx:207 +#: src/view/com/util/error/ErrorMessage.tsx:55 +#: src/view/com/util/error/ErrorScreen.tsx:72 +msgid "Retry" +msgstr "重試" + +#: src/components/Error.tsx:98 +#: src/view/screens/ProfileList.tsx:971 +msgid "Return to previous page" +msgstr "返回上一頁" + +#: src/view/screens/NotFound.tsx:59 +msgid "Returns to home page" +msgstr "返回首頁" + +#: src/view/screens/NotFound.tsx:58 +#: src/view/screens/ProfileFeed.tsx:112 +msgid "Returns to previous page" +msgstr "返回上一頁" + +#: src/components/dialogs/BirthDateSettings.tsx:125 +#: src/view/com/composer/GifAltText.tsx:162 +#: src/view/com/composer/GifAltText.tsx:168 +#: src/view/com/modals/ChangeHandle.tsx:168 +#: src/view/com/modals/CreateOrEditList.tsx:326 +#: src/view/com/modals/EditProfile.tsx:225 +msgid "Save" +msgstr "儲存" + +#: src/view/com/lightbox/Lightbox.tsx:133 +#: src/view/com/modals/CreateOrEditList.tsx:334 +msgctxt "action" +msgid "Save" +msgstr "儲存" + +#: src/view/com/modals/AltImage.tsx:132 +msgid "Save alt text" +msgstr "儲存替代文字" + +#: src/components/dialogs/BirthDateSettings.tsx:119 +msgid "Save birthday" +msgstr "儲存生日" + +#: src/view/com/modals/EditProfile.tsx:233 +msgid "Save Changes" +msgstr "儲存更改" + +#: src/view/com/modals/ChangeHandle.tsx:165 +msgid "Save handle change" +msgstr "儲存帳號代碼更改" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:169 +msgid "Save image crop" +msgstr "儲存圖片裁剪" + +#: src/view/screens/ProfileFeed.tsx:331 +#: src/view/screens/ProfileFeed.tsx:337 +msgid "Save to my feeds" +msgstr "儲存到我的動態源" + +#: src/view/screens/SavedFeeds.tsx:145 +msgid "Saved Feeds" +msgstr "已儲存之動態源" + +#: src/view/com/lightbox/Lightbox.tsx:82 +msgid "Saved to your camera roll" +msgstr "儲存至裝置相簿" + +#: src/view/screens/ProfileFeed.tsx:200 +#: src/view/screens/ProfileList.tsx:300 +msgid "Saved to your feeds" +msgstr "儲存到您的動態源" + +#: src/view/com/modals/EditProfile.tsx:226 +msgid "Saves any changes to your profile" +msgstr "儲存個人檔案中所做的變更" + +#: src/view/com/modals/ChangeHandle.tsx:166 +msgid "Saves handle change to {handle}" +msgstr "儲存帳號代碼更改至 {handle}" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:170 +msgid "Saves image crop settings" +msgstr "儲存圖片裁剪設定" + +#: src/components/dms/ChatEmptyPill.tsx:33 +#: src/components/NewskieDialog.tsx:72 +msgid "Say hello!" +msgstr "說句「你好!👋」" + +#: src/screens/Onboarding/index.tsx:33 +msgid "Science" +msgstr "科學" + +#: src/view/screens/ProfileList.tsx:927 +msgid "Scroll to top" +msgstr "滾動到頂部" + +#: src/components/dms/dialogs/SearchablePeopleList.tsx:504 +#: src/Navigation.tsx:494 +#: src/view/com/auth/LoggedOut.tsx:123 +#: src/view/com/modals/ListAddRemoveUsers.tsx:75 +#: src/view/com/util/forms/SearchInput.tsx:67 +#: src/view/com/util/forms/SearchInput.tsx:79 +#: src/view/screens/Search/Search.tsx:421 +#: src/view/screens/Search/Search.tsx:791 +#: src/view/screens/Search/Search.tsx:813 +#: src/view/shell/bottom-bar/BottomBar.tsx:179 +#: src/view/shell/desktop/LeftNav.tsx:343 +#: src/view/shell/desktop/Search.tsx:194 +#: src/view/shell/desktop/Search.tsx:203 +#: src/view/shell/Drawer.tsx:394 +#: src/view/shell/Drawer.tsx:395 +msgid "Search" +msgstr "搜尋" + +#: src/view/shell/desktop/Search.tsx:235 +msgid "Search for \"{query}\"" +msgstr "搜尋「{query}」" + +#: src/view/screens/Search/Search.tsx:869 +msgid "Search for \"{searchText}\"" +msgstr "搜尋「{searchText}」" + +#: src/components/TagMenu/index.tsx:145 +msgid "Search for all posts by @{authorHandle} with tag {displayTag}" +msgstr "搜尋所有由 @{authorHandle} 發佈並具有標籤 {displayTag} 的貼文" + +#: src/components/TagMenu/index.tsx:94 +msgid "Search for all posts with tag {displayTag}" +msgstr "搜尋所有具有標籤 {displayTag} 的貼文" + +#: src/view/com/auth/LoggedOut.tsx:105 +#: src/view/com/auth/LoggedOut.tsx:106 +#: src/view/com/modals/ListAddRemoveUsers.tsx:70 +msgid "Search for users" +msgstr "搜尋用戶" + +#: src/components/dialogs/GifSelect.ios.tsx:159 +#: src/components/dialogs/GifSelect.tsx:169 +msgid "Search GIFs" +msgstr "搜尋 GIF" + +#: src/components/dms/dialogs/SearchablePeopleList.tsx:524 +#: src/components/dms/dialogs/SearchablePeopleList.tsx:525 +msgid "Search profiles" +msgstr "搜尋用戶" + +#: src/components/dialogs/GifSelect.ios.tsx:160 +#: src/components/dialogs/GifSelect.tsx:170 +msgid "Search Tenor" +msgstr "搜尋 Tenor" + +#: src/view/com/modals/ChangeEmail.tsx:105 +msgid "Security Step Required" +msgstr "所需的安全步驟" + +#: src/components/TagMenu/index.web.tsx:66 +msgid "See {truncatedTag} posts" +msgstr "搜尋 {truncatedTag}" + +#: src/components/TagMenu/index.web.tsx:83 +msgid "See {truncatedTag} posts by user" +msgstr "查看該用戶包含 {truncatedTag} 的貼文" + +#: src/components/TagMenu/index.tsx:128 +msgid "See <0>{displayTag} posts" +msgstr "搜尋 <0>{displayTag}" + +#: src/components/TagMenu/index.tsx:187 +msgid "See <0>{displayTag} posts by this user" +msgstr "查看該用戶包含 <0>{displayTag} 的貼文" + +#: src/view/screens/SavedFeeds.tsx:187 +msgid "See this guide" +msgstr "查看指南" + +#: src/view/com/util/Selector.tsx:106 +msgid "Select {item}" +msgstr "選擇 {item}" + +#: src/screens/Onboarding/StepProfile/AvatarCreatorItems.tsx:67 +msgid "Select a color" +msgstr "選擇一個顏色" + +#: src/screens/Login/ChooseAccountForm.tsx:85 +msgid "Select account" +msgstr "選擇帳號" + +#: src/screens/Onboarding/StepProfile/AvatarCircle.tsx:66 +msgid "Select an avatar" +msgstr "選擇一個頭像" + +#: src/screens/Onboarding/StepProfile/AvatarCreatorItems.tsx:65 +msgid "Select an emoji" +msgstr "選擇一個表情符號" + +#: src/screens/Login/index.tsx:120 +msgid "Select from an existing account" +msgstr "從現有帳號中選擇" + +#: src/view/com/composer/photos/SelectGifBtn.tsx:35 +msgid "Select GIF" +msgstr "選擇 GIF" + +#: src/components/dialogs/GifSelect.shared.tsx:29 +msgid "Select GIF \"{0}\"" +msgstr "選擇 GIF「{0}」" + +#: src/view/screens/LanguageSettings.tsx:301 +msgid "Select languages" +msgstr "選擇語言" + +#: src/components/ReportDialog/SelectLabelerView.tsx:30 +msgid "Select moderator" +msgstr "選擇內容管理服務提供者" + +#: src/view/com/util/Selector.tsx:107 +msgid "Select option {i} of {numItems}" +msgstr "選擇 {numItems} 個項目中的第 {i} 項" + +#: src/screens/Onboarding/StepProfile/AvatarCreatorItems.tsx:83 +msgid "Select the {emojiName} emoji as your avatar" +msgstr "選擇 {emojiName} 表情符號作為您的頭像" + +#: src/components/ReportDialog/SubmitView.tsx:135 +msgid "Select the moderation service(s) to report to" +msgstr "選擇要檢舉的內容管理服務提供者" + +#: src/view/com/auth/server-input/index.tsx:82 +msgid "Select the service that hosts your data." +msgstr "選擇用來託管您的資料的服務商。" + +#: src/view/screens/LanguageSettings.tsx:283 +msgid "Select which languages you want your subscribed feeds to include. If none are selected, all languages will be shown." +msgstr "選擇您希望訂閱動態源中所包含的語言。未選擇任何語言時會預設顯示所有語言。" + +#: src/view/screens/LanguageSettings.tsx:99 +msgid "Select your app language for the default text to display in the app." +msgstr "選擇應用程式中的預設語言。" + +#: src/screens/Signup/StepInfo/index.tsx:135 +msgid "Select your date of birth" +msgstr "選擇您的出生日期" + +#: src/screens/Onboarding/StepInterests/index.tsx:201 +msgid "Select your interests from the options below" +msgstr "從下面選擇您感興趣的選項" + +#: src/view/screens/LanguageSettings.tsx:192 +msgid "Select your preferred language for translations in your feed." +msgstr "選擇您在動態中翻譯的偏好目標語言。" + +#: src/components/dms/ChatEmptyPill.tsx:38 +msgid "Send a neat website!" +msgstr "發送一個妙趣的網站!" + +#: src/view/com/modals/VerifyEmail.tsx:210 +#: src/view/com/modals/VerifyEmail.tsx:212 +msgid "Send Confirmation Email" +msgstr "發送確認電子郵件" + +#: src/view/com/modals/DeleteAccount.tsx:149 +msgid "Send email" +msgstr "發送電子郵件" + +#: src/view/com/modals/DeleteAccount.tsx:162 +msgctxt "action" +msgid "Send Email" +msgstr "發送電子郵件" + +#: src/view/shell/Drawer.tsx:329 +#: src/view/shell/Drawer.tsx:350 +msgid "Send feedback" +msgstr "提交意見" + +#: src/screens/Messages/Conversation/MessageInput.tsx:163 +#: src/screens/Messages/Conversation/MessageInput.web.tsx:155 +msgid "Send message" +msgstr "重送訊息" + +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:59 +msgid "Send post to..." +msgstr "傳送貼文給…" + +#: src/components/dms/ReportDialog.tsx:234 +#: src/components/dms/ReportDialog.tsx:237 +#: src/components/ReportDialog/SubmitView.tsx:215 +#: src/components/ReportDialog/SubmitView.tsx:219 +msgid "Send report" +msgstr "提交檢舉" + +#: src/components/ReportDialog/SelectLabelerView.tsx:44 +msgid "Send report to {0}" +msgstr "將檢舉提交至 {0}" + +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:119 +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:122 +msgid "Send verification email" +msgstr "發送驗證電子郵件" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:296 +#: src/view/com/util/forms/PostDropdownBtn.tsx:299 +msgid "Send via direct message" +msgstr "透過私人訊息發送" + +#: src/view/com/modals/DeleteAccount.tsx:151 +msgid "Sends email with confirmation code for account deletion" +msgstr "發送包含帳號刪除確認碼的電子郵件" + +#: src/view/com/auth/server-input/index.tsx:114 +msgid "Server address" +msgstr "伺服器地址" + +#: src/screens/Moderation/index.tsx:304 +msgid "Set birthdate" +msgstr "設定生日" + +#: src/screens/Login/SetNewPasswordForm.tsx:102 +msgid "Set new password" +msgstr "設定新密碼" + +#: src/view/screens/PreferencesFollowingFeed.tsx:224 +msgid "Set this setting to \"No\" to hide all quote posts from your feed. Reposts will still be visible." +msgstr "將此選項設為「關」以隱藏動態中所有引用的貼文,但轉貼依然會顯示。" + +#: src/view/screens/PreferencesFollowingFeed.tsx:121 +msgid "Set this setting to \"No\" to hide all replies from your feed." +msgstr "將此選項設為「關」以隱藏動態中所有回覆貼文。" + +#: src/view/screens/PreferencesFollowingFeed.tsx:190 +msgid "Set this setting to \"No\" to hide all reposts from your feed." +msgstr "將此選項設為「關」以隱藏動態的所有轉貼貼文。" + +#: src/view/screens/PreferencesThreads.tsx:122 +msgid "Set this setting to \"Yes\" to show replies in a threaded view. This is an experimental feature." +msgstr "將此選項設為「開」以單頁顯示樹狀回覆,這是一項實驗性功能。" + +#: src/view/screens/PreferencesFollowingFeed.tsx:260 +msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your Following feed. This is an experimental feature." +msgstr "將此選項設為「是」以在「Following」動態源中顯示您已儲存之動態源中的選錄貼文,這是一項實驗性功能。" + +#: src/screens/Onboarding/Layout.tsx:48 +msgid "Set up your account" +msgstr "設定您的帳號" + +#: src/view/com/modals/ChangeHandle.tsx:261 +msgid "Sets Bluesky username" +msgstr "設定 Bluesky 帳號代碼" + +#: src/view/screens/Settings/index.tsx:461 +msgid "Sets color theme to dark" +msgstr "將色彩主題設定為深色" + +#: src/view/screens/Settings/index.tsx:454 +msgid "Sets color theme to light" +msgstr "將色彩主題設定為亮色" + +#: src/view/screens/Settings/index.tsx:448 +msgid "Sets color theme to system setting" +msgstr "將色彩主題設定為跟隨系統" + +#: src/view/screens/Settings/index.tsx:487 +msgid "Sets dark theme to the dark theme" +msgstr "將深色主題設定為深色" + +#: src/view/screens/Settings/index.tsx:480 +msgid "Sets dark theme to the dim theme" +msgstr "將深色主題設定為昏暗" + +#: src/screens/Login/ForgotPasswordForm.tsx:113 +msgid "Sets email for password reset" +msgstr "設定用於重設密碼的電子郵件" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:146 +msgid "Sets image aspect ratio to square" +msgstr "將圖片比例設定為正方形" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:136 +msgid "Sets image aspect ratio to tall" +msgstr "將圖片比例設定為高" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:126 +msgid "Sets image aspect ratio to wide" +msgstr "將圖片比例設定為寬" + +#: src/Navigation.tsx:145 +#: src/view/screens/Settings/index.tsx:332 +#: src/view/shell/desktop/LeftNav.tsx:389 +#: src/view/shell/Drawer.tsx:559 +#: src/view/shell/Drawer.tsx:560 +msgid "Settings" +msgstr "設定" + +#: src/view/com/modals/SelfLabel.tsx:126 +msgid "Sexual activity or erotic nudity." +msgstr "性行為或性暗示裸露。" + +#: src/lib/moderation/useGlobalLabelStrings.ts:38 +msgid "Sexually Suggestive" +msgstr "性暗示" + +#: src/view/com/lightbox/Lightbox.tsx:142 +msgctxt "action" +msgid "Share" +msgstr "分享" + +#: src/view/com/profile/ProfileMenu.tsx:220 +#: src/view/com/profile/ProfileMenu.tsx:229 +#: src/view/com/util/forms/PostDropdownBtn.tsx:307 +#: src/view/com/util/forms/PostDropdownBtn.tsx:316 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:300 +#: src/view/screens/ProfileList.tsx:428 +msgid "Share" +msgstr "分享" + +#: src/components/dms/ChatEmptyPill.tsx:37 +msgid "Share a cool story!" +msgstr "分享一個有趣的故事!" + +#: src/components/dms/ChatEmptyPill.tsx:36 +msgid "Share a fun fact!" +msgstr "分享一個趣聞!📰" + +#: src/view/com/profile/ProfileMenu.tsx:378 +#: src/view/com/util/forms/PostDropdownBtn.tsx:461 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:316 +msgid "Share anyway" +msgstr "仍然分享" + +#: src/view/screens/ProfileFeed.tsx:357 +#: src/view/screens/ProfileFeed.tsx:359 +msgid "Share feed" +msgstr "分享動態源" + +#: src/view/com/modals/LinkWarning.tsx:89 +#: src/view/com/modals/LinkWarning.tsx:95 +msgid "Share Link" +msgstr "分享連結" + +#: src/components/dms/ChatEmptyPill.tsx:34 +msgid "Share your favorite feed!" +msgstr "分享你喜愛的動態!" + +#: src/view/com/modals/LinkWarning.tsx:92 +msgid "Shares the linked website" +msgstr "分享網站的連結" + +#: src/components/moderation/ContentHider.tsx:116 +#: src/components/moderation/LabelPreference.tsx:136 +#: src/components/moderation/PostHider.tsx:122 +#: src/view/screens/Settings/index.tsx:381 +msgid "Show" +msgstr "顯示" + +#: src/view/com/util/post-embeds/GifEmbed.tsx:169 +msgid "Show alt text" +msgstr "顯示替代文字" + +#: src/components/moderation/ScreenHider.tsx:169 +#: src/components/moderation/ScreenHider.tsx:172 +msgid "Show anyway" +msgstr "仍然顯示" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:27 +#: src/lib/moderation/useLabelBehaviorDescription.ts:63 +msgid "Show badge" +msgstr "顯示標記" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:61 +msgid "Show badge and filter from feeds" +msgstr "顯示標記並從動態源中篩選" + +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:215 +msgid "Show follows similar to {0}" +msgstr "顯示類似於 {0} 的跟隨者" + +#: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:23 +msgid "Show hidden replies" +msgstr "顯示隱藏回覆" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:346 +#: src/view/com/util/forms/PostDropdownBtn.tsx:348 +msgid "Show less like this" +msgstr "減少顯示此類內容" + +#: src/view/com/post-thread/PostThreadItem.tsx:533 +#: src/view/com/post/Post.tsx:227 +#: src/view/com/posts/FeedItem.tsx:396 +msgid "Show More" +msgstr "顯示更多" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:338 +#: src/view/com/util/forms/PostDropdownBtn.tsx:340 +msgid "Show more like this" +msgstr "顯示更多此類內容" + +#: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:23 +msgid "Show muted replies" +msgstr "顯示靜音回覆" + +#: src/view/screens/PreferencesFollowingFeed.tsx:257 +msgid "Show Posts from My Feeds" +msgstr "顯示來自我的動態源之貼文" + +#: src/view/screens/PreferencesFollowingFeed.tsx:221 +msgid "Show Quote Posts" +msgstr "顯示引用貼文" + +#: src/view/screens/PreferencesFollowingFeed.tsx:118 +msgid "Show Replies" +msgstr "顯示回覆" + +#: src/view/screens/PreferencesThreads.tsx:100 +msgid "Show replies by people you follow before all other replies." +msgstr "在所有其他回覆之前顯示您跟隨的人的回覆。" + +#: src/view/screens/PreferencesFollowingFeed.tsx:187 +msgid "Show Reposts" +msgstr "顯示轉貼貼文" + +#: src/components/moderation/ContentHider.tsx:69 +#: src/components/moderation/PostHider.tsx:79 +msgid "Show the content" +msgstr "顯示內容" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:58 +msgid "Show warning" +msgstr "顯示警告" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:56 +msgid "Show warning and filter from feeds" +msgstr "顯示警告並從動態中篩選" + +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:130 +msgid "Shows posts from {0} in your feed" +msgstr "在您的動態中顯示來自 {0} 的貼文" + +#: src/components/dialogs/Signin.tsx:97 +#: src/components/dialogs/Signin.tsx:99 +#: src/screens/Login/index.tsx:100 +#: src/screens/Login/index.tsx:119 +#: src/screens/Login/LoginForm.tsx:154 +#: src/view/com/auth/SplashScreen.tsx:63 +#: src/view/com/auth/SplashScreen.tsx:72 +#: src/view/com/auth/SplashScreen.web.tsx:112 +#: src/view/com/auth/SplashScreen.web.tsx:121 +#: src/view/shell/bottom-bar/BottomBar.tsx:312 +#: src/view/shell/bottom-bar/BottomBar.tsx:313 +#: src/view/shell/bottom-bar/BottomBar.tsx:315 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:204 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:205 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:207 +#: src/view/shell/NavSignupCard.tsx:69 +#: src/view/shell/NavSignupCard.tsx:70 +#: src/view/shell/NavSignupCard.tsx:72 +msgid "Sign in" +msgstr "登入" + +#: src/components/AccountList.tsx:114 +msgid "Sign in as {0}" +msgstr "以 {0} 登入" + +#: src/screens/Login/ChooseAccountForm.tsx:88 +msgid "Sign in as..." +msgstr "登入為…" + +#: src/components/dialogs/Signin.tsx:75 +msgid "Sign in or create your account to join the conversation!" +msgstr "登入或建立您的帳號即可加入對話!" + +#: src/components/dialogs/Signin.tsx:46 +msgid "Sign into Bluesky or create a new account" +msgstr "登入 Bluesky 或建立新帳號" + +#: src/view/screens/Settings/index.tsx:129 +#: src/view/screens/Settings/index.tsx:133 +msgid "Sign out" +msgstr "登出" + +#: src/view/shell/bottom-bar/BottomBar.tsx:302 +#: src/view/shell/bottom-bar/BottomBar.tsx:303 +#: src/view/shell/bottom-bar/BottomBar.tsx:305 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:194 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:195 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:197 +#: src/view/shell/NavSignupCard.tsx:60 +#: src/view/shell/NavSignupCard.tsx:61 +#: src/view/shell/NavSignupCard.tsx:63 +msgid "Sign up" +msgstr "註冊" + +#: src/view/shell/NavSignupCard.tsx:47 +msgid "Sign up or sign in to join the conversation" +msgstr "註冊或登入即可參與對話" + +#: src/components/moderation/ScreenHider.tsx:97 +#: src/lib/moderation/useGlobalLabelStrings.ts:28 +msgid "Sign-in Required" +msgstr "需要登入" + +#: src/view/screens/Settings/index.tsx:391 +msgid "Signed in as" +msgstr "登入身分" + +#: src/lib/hooks/useAccountSwitcher.ts:44 +#: src/screens/Login/ChooseAccountForm.tsx:60 +msgid "Signed in as @{0}" +msgstr "以 @{0} 身分登入" + +#: src/screens/Onboarding/StepInterests/index.tsx:240 +msgid "Skip" +msgstr "跳過" + +#: src/screens/Onboarding/StepInterests/index.tsx:237 +msgid "Skip this flow" +msgstr "跳過此流程" + +#: src/screens/Onboarding/index.tsx:37 +msgid "Software Dev" +msgstr "軟體開發" + +#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 +#: src/view/com/threadgate/WhoCanReply.tsx:67 +#: src/view/com/threadgate/WhoCanReply.tsx:124 +msgid "Some people can reply" +msgstr "僅部分人可以回覆" + +#: src/screens/Messages/Conversation/index.tsx:106 +msgid "Something went wrong" +msgstr "發生了一些問題" + +#: src/screens/Deactivated.tsx:94 +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:59 +msgid "Something went wrong, please try again" +msgstr "發生了一些問題,請重試" + +#: src/components/ReportDialog/index.tsx:59 +#: src/screens/Moderation/index.tsx:114 +#: src/screens/Profile/Sections/Labels.tsx:87 +msgid "Something went wrong, please try again." +msgstr "發生了一些問題,請重試。" + +#: src/App.native.tsx:92 +#: src/App.web.tsx:74 +msgid "Sorry! Your session expired. Please log in again." +msgstr "抱歉!您的登入會話已過期。請重新登入。" + +#: src/view/screens/PreferencesThreads.tsx:69 +msgid "Sort Replies" +msgstr "排序回覆" + +#: src/view/screens/PreferencesThreads.tsx:72 +msgid "Sort replies to the same post by:" +msgstr "對同一貼文的回覆進行排序:" + +#: src/components/moderation/LabelsOnMeDialog.tsx:168 +msgid "Source: <0>{0}" +msgstr "來源:<0>{0}" + +#: src/lib/moderation/useReportOptions.ts:66 +#: src/lib/moderation/useReportOptions.ts:79 +msgid "Spam" +msgstr "垃圾訊息" + +#: src/lib/moderation/useReportOptions.ts:54 +msgid "Spam; excessive mentions or replies" +msgstr "垃圾訊息、過多的提及或回覆" + +#: src/screens/Onboarding/index.tsx:27 +msgid "Sports" +msgstr "運動" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:145 +msgid "Square" +msgstr "方塊" + +#: src/components/dms/dialogs/NewChatDialog.tsx:61 +msgid "Start a new chat" +msgstr "開始新對話" + +#: src/components/dms/dialogs/SearchablePeopleList.tsx:371 +msgid "Start chat with {displayName}" +msgstr "與 {displayName} 開始對話" + +#: src/components/dms/MessagesNUX.tsx:161 +msgid "Start chatting" +msgstr "開始對話" + +#: src/view/screens/Settings/index.tsx:963 +msgid "Status Page" +msgstr "服務運作狀態頁面" + +#: src/screens/Signup/index.tsx:154 +msgid "Step {0} of {1}" +msgstr "第 {0} 步(共 {1} 步)" + +#: src/view/screens/Settings/index.tsx:304 +msgid "Storage cleared, you need to restart the app now." +msgstr "已清除儲存資料,您需要立即重啟應用程式。" + +#: src/Navigation.tsx:224 +#: src/view/screens/Settings/index.tsx:863 +msgid "Storybook" +msgstr "故事書" + +#: src/components/moderation/LabelsOnMeDialog.tsx:290 +#: src/components/moderation/LabelsOnMeDialog.tsx:291 +#: src/screens/Messages/Conversation/ChatDisabled.tsx:142 +#: src/screens/Messages/Conversation/ChatDisabled.tsx:143 +msgid "Submit" +msgstr "提交" + +#: src/view/screens/ProfileList.tsx:644 +msgid "Subscribe" +msgstr "訂閱" + +#: src/screens/Profile/Sections/Labels.tsx:201 +msgid "Subscribe to @{0} to use these labels:" +msgstr "訂閱 @{0} 以使用這些標記:" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:230 +msgid "Subscribe to Labeler" +msgstr "訂閱標記者" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:194 +msgid "Subscribe to this labeler" +msgstr "訂閱這個標記者" + +#: src/view/screens/ProfileList.tsx:640 +msgid "Subscribe to this list" +msgstr "訂閱這個列表" + +#: src/view/screens/Search/Explore.tsx:331 +msgid "Suggested accounts" +msgstr "推薦的帳號" + +#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:65 +msgid "Suggested for you" +msgstr "為您推薦" + +#: src/view/com/modals/SelfLabel.tsx:96 +msgid "Suggestive" +msgstr "性暗示" + +#: src/Navigation.tsx:239 +#: src/view/screens/Support.tsx:30 +#: src/view/screens/Support.tsx:33 +msgid "Support" +msgstr "支援" + +#: src/components/dialogs/SwitchAccount.tsx:47 +#: src/components/dialogs/SwitchAccount.tsx:50 +msgid "Switch Account" +msgstr "切換帳號" + +#: src/view/screens/Settings/index.tsx:160 +msgid "Switch to {0}" +msgstr "切換到 {0}" + +#: src/view/screens/Settings/index.tsx:161 +msgid "Switches the account you are logged in to" +msgstr "切換您登入的帳號" + +#: src/view/screens/Settings/index.tsx:445 +msgid "System" +msgstr "系統" + +#: src/view/screens/Settings/index.tsx:851 +msgid "System log" +msgstr "系統日誌" + +#: src/components/dialogs/MutedWords.tsx:323 +msgid "tag" +msgstr "標籤" + +#: src/components/TagMenu/index.tsx:78 +msgid "Tag menu: {displayTag}" +msgstr "標籤選單:{displayTag}" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:135 +msgid "Tall" +msgstr "高" + +#: src/view/com/util/images/AutoSizedImage.tsx:70 +msgid "Tap to view fully" +msgstr "點擊查看完整內容" + +#: src/screens/Onboarding/index.tsx:36 +msgid "Tech" +msgstr "科技" + +#: src/components/dms/ChatEmptyPill.tsx:35 +msgid "Tell a joke!" +msgstr "說個笑話!🤡" + +#: src/view/shell/desktop/RightNav.tsx:86 +msgid "Terms" +msgstr "條款" + +#: src/Navigation.tsx:249 +#: src/screens/Signup/StepInfo/Policies.tsx:49 +#: src/view/screens/Settings/index.tsx:951 +#: src/view/screens/TermsOfService.tsx:29 +#: src/view/shell/Drawer.tsx:279 +msgid "Terms of Service" +msgstr "服務條款" + +#: src/lib/moderation/useReportOptions.ts:59 +#: src/lib/moderation/useReportOptions.ts:93 +#: src/lib/moderation/useReportOptions.ts:101 +msgid "Terms used violate community standards" +msgstr "所使用的文字違反了社群標準" + +#: src/components/dialogs/MutedWords.tsx:323 +msgid "text" +msgstr "文字" + +#: src/components/moderation/LabelsOnMeDialog.tsx:254 +#: src/screens/Messages/Conversation/ChatDisabled.tsx:108 +msgid "Text input field" +msgstr "文字輸入框" + +#: src/components/dms/ReportDialog.tsx:134 +#: src/components/ReportDialog/SubmitView.tsx:77 +msgid "Thank you. Your report has been sent." +msgstr "謝謝,您的檢舉已提交。" + +#: src/view/com/modals/ChangeHandle.tsx:459 +msgid "That contains the following:" +msgstr "其中包含以下內容:" + +#: src/screens/Signup/index.tsx:87 +msgid "That handle is already taken." +msgstr "這個帳號代碼已被使用。" + +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:310 +#: src/view/com/profile/ProfileMenu.tsx:354 +msgid "The account will be able to interact with you after unblocking." +msgstr "解除封鎖後,該帳號將能夠與您互動。" + +#: src/view/screens/CommunityGuidelines.tsx:36 +msgid "The Community Guidelines have been moved to <0/>" +msgstr "社群準則已移動到 <0/>" + +#: src/view/screens/CopyrightPolicy.tsx:33 +msgid "The Copyright Policy has been moved to <0/>" +msgstr "版權政策已移動到 <0/>" + +#: src/view/com/posts/FeedShutdownMsg.tsx:66 +msgid "The feed has been replaced with Discover." +msgstr "此動態源已由「Discover」取代。" + +#: src/components/moderation/LabelsOnMeDialog.tsx:65 +msgid "The following labels were applied to your account." +msgstr "以下標記已套用到您的帳號。" + +#: src/components/moderation/LabelsOnMeDialog.tsx:66 +msgid "The following labels were applied to your content." +msgstr "以下標記已套用到您的內容。" + +#: src/screens/Onboarding/Layout.tsx:58 +msgid "The following steps will help customize your Bluesky experience." +msgstr "以下步驟將幫助自訂您的 Bluesky 體驗。" + +#: src/view/com/post-thread/PostThread.tsx:189 +#: src/view/com/post-thread/PostThread.tsx:201 +msgid "The post may have been deleted." +msgstr "這則貼文可能已被刪除。" + +#: src/view/screens/PrivacyPolicy.tsx:33 +msgid "The Privacy Policy has been moved to <0/>" +msgstr "隱私政策已移動到 <0/>" + +#: src/view/screens/Support.tsx:36 +msgid "The support form has been moved. If you need help, please <0/> or visit {HELP_DESK_URL} to get in touch with us." +msgstr "支援表單已移至別處。如果需協助,請<0/>或前往 {HELP_DESK_URL} 與我們聯繫。" + +#: src/view/screens/TermsOfService.tsx:33 +msgid "The Terms of Service have been moved to" +msgstr "服務條款已遷移到" + +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:86 +msgid "There is no time limit for account deactivation, come back any time." +msgstr "帳號停用沒有時間限制,隨時都可以重新啟用。" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:115 +#: src/view/screens/ProfileFeed.tsx:541 +msgid "There was an an issue contacting the server, please check your internet connection and try again." +msgstr "連線至伺服器時出現問題,請檢查您的網路連線並重試。" + +#: src/view/com/posts/FeedErrorMessage.tsx:145 +msgid "There was an an issue removing this feed. Please check your internet connection and try again." +msgstr "刪除動態源時出現問題,請檢查您的網路連線並重試。" + +#: src/view/com/posts/FeedShutdownMsg.tsx:52 +#: src/view/com/posts/FeedShutdownMsg.tsx:70 +#: src/view/screens/ProfileFeed.tsx:205 +msgid "There was an an issue updating your feeds, please check your internet connection and try again." +msgstr "更新動態時出現問題,請檢查您的網路連線並重試。" + +#: src/components/dialogs/GifSelect.ios.tsx:197 +#: src/components/dialogs/GifSelect.tsx:213 +msgid "There was an issue connecting to Tenor." +msgstr "連線到 Tenor 時出現問題。" + +#: src/view/screens/ProfileFeed.tsx:233 +#: src/view/screens/ProfileList.tsx:303 +#: src/view/screens/ProfileList.tsx:322 +#: src/view/screens/SavedFeeds.tsx:237 +#: src/view/screens/SavedFeeds.tsx:263 +#: src/view/screens/SavedFeeds.tsx:289 +msgid "There was an issue contacting the server" +msgstr "連線伺服器時出現問題" + +#: src/view/com/feeds/FeedSourceCard.tsx:128 +#: src/view/com/feeds/FeedSourceCard.tsx:141 +msgid "There was an issue contacting your server" +msgstr "連線伺服器時出現問題" + +#: src/view/com/notifications/Feed.tsx:126 +msgid "There was an issue fetching notifications. Tap here to try again." +msgstr "取得通知時發生問題,點擊這裡重試。" + +#: src/view/com/posts/Feed.tsx:299 +msgid "There was an issue fetching posts. Tap here to try again." +msgstr "取得貼文時發生問題,點擊這裡重試。" + +#: src/view/com/lists/ListMembers.tsx:172 +msgid "There was an issue fetching the list. Tap here to try again." +msgstr "取得列表時發生問題,點擊這裡重試。" + +#: src/view/com/feeds/ProfileFeedgens.tsx:153 +#: src/view/com/lists/ProfileLists.tsx:160 +msgid "There was an issue fetching your lists. Tap here to try again." +msgstr "取得列表時發生問題,點擊這裡重試。" + +#: src/components/dms/ReportDialog.tsx:222 +#: src/components/ReportDialog/SubmitView.tsx:82 +msgid "There was an issue sending your report. Please check your internet connection." +msgstr "提交您的檢舉時出現問題,請檢查您的網路連線。" + +#: src/view/screens/AppPasswords.tsx:70 +msgid "There was an issue with fetching your app passwords" +msgstr "取得應用程式專用密碼時發生問題" + +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:107 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:129 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:143 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:99 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:111 +#: src/view/com/profile/ProfileMenu.tsx:112 +#: src/view/com/profile/ProfileMenu.tsx:123 +#: src/view/com/profile/ProfileMenu.tsx:138 +#: src/view/com/profile/ProfileMenu.tsx:149 +#: src/view/com/profile/ProfileMenu.tsx:163 +#: src/view/com/profile/ProfileMenu.tsx:176 +msgid "There was an issue! {0}" +msgstr "發生問題!{0}" + +#: src/view/screens/ProfileList.tsx:335 +#: src/view/screens/ProfileList.tsx:349 +#: src/view/screens/ProfileList.tsx:363 +#: src/view/screens/ProfileList.tsx:377 +msgid "There was an issue. Please check your internet connection and try again." +msgstr "發生問題了。請檢查您的網路連線並重試。" + +#: src/components/dialogs/GifSelect.ios.tsx:239 +#: src/components/dialogs/GifSelect.tsx:257 +#: src/view/com/util/ErrorBoundary.tsx:57 +msgid "There was an unexpected issue in the application. Please let us know if this happened to you!" +msgstr "應用程式中發生了意外問題。請告訴我們是否發生在您身上!" + +#: src/screens/SignupQueued.tsx:112 +msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." +msgstr "Bluesky 迎來了大量新用戶!我們將儘快啟用您的帳號。" + +#: src/components/moderation/ScreenHider.tsx:116 +msgid "This {screenDescription} has been flagged:" +msgstr "{screenDescription} 已被標記:" + +#: src/components/moderation/ScreenHider.tsx:111 +msgid "This account has requested that users sign in to view their profile." +msgstr "此帳號要求使用者登入後才能查看其個人檔案。" + +#: src/components/dms/BlockedByListDialog.tsx:34 +msgid "This account is blocked by one or more of your moderation lists. To unblock, please visit the lists directly and remove this user." +msgstr "此帳號已被一個或多個內容管理清單封鎖。若要解除封鎖,請直接瀏覽這些清單並刪除此使用者。" + +#: src/components/moderation/LabelsOnMeDialog.tsx:239 +msgid "This appeal will be sent to <0>{0}." +msgstr "此申訴將被提交至 <0>{0}。" + +#: src/screens/Messages/Conversation/ChatDisabled.tsx:104 +msgid "This appeal will be sent to Bluesky's moderation service." +msgstr "此申訴將發送至 Bluesky 的內容管理服務。" + +#: src/screens/Messages/Conversation/MessageListError.tsx:18 +msgid "This chat was disconnected" +msgstr "對話已中斷連線" + +#: src/lib/moderation/useGlobalLabelStrings.ts:19 +msgid "This content has been hidden by the moderators." +msgstr "此內容已被內容管理者隱藏。" + +#: src/lib/moderation/useGlobalLabelStrings.ts:24 +msgid "This content has received a general warning from moderators." +msgstr "此內容已套用內容管理提供者所標記的普通警告。" + +#: src/components/dialogs/EmbedConsent.tsx:64 +msgid "This content is hosted by {0}. Do you want to enable external media?" +msgstr "此內容由 {0} 託管。是否要啟用外部媒體?" + +#: src/components/moderation/ModerationDetailsDialog.tsx:77 +#: src/lib/moderation/useModerationCauseDescription.ts:79 +msgid "This content is not available because one of the users involved has blocked the other." +msgstr "由於其中一個用戶封鎖了另一個用戶,無法查看此內容。" + +#: src/view/com/posts/FeedErrorMessage.tsx:114 +msgid "This content is not viewable without a Bluesky account." +msgstr "沒有 Bluesky 帳號,無法查看此內容。" + +#: src/screens/Messages/List/ChatListItem.tsx:213 +msgid "This conversation is with a deleted or a deactivated account. Press for options." +msgstr "此對話是與已刪除或停用的帳號進行的。點擊以查看選項。" + +#: src/view/screens/Settings/ExportCarDialog.tsx:93 +msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost." +msgstr "此功能目前為測試版本。您可以在<0>這篇部落格文章中瞭解更多有關資訊。" + +#: src/view/com/posts/FeedErrorMessage.tsx:120 +msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." +msgstr "此動態源由於目前使用人數眾多而暫時無法使用。請稍後再試。" + +#: src/view/com/posts/CustomFeedEmptyState.tsx:37 +msgid "This feed is empty! You may need to follow more users or tune your language settings." +msgstr "這個動態源是空的!您或許需要先跟隨更多的人或檢查您的語言設定。" + +#: src/view/screens/ProfileFeed.tsx:471 +#: src/view/screens/ProfileList.tsx:729 +msgid "This feed is empty." +msgstr "這裡是空的。" + +#: src/view/com/posts/FeedShutdownMsg.tsx:97 +msgid "This feed is no longer online. We are showing <0>Discover instead." +msgstr "此動態源已經下線。我們將展示「<0>Discover」動態源。" + +#: src/components/dialogs/BirthDateSettings.tsx:41 +msgid "This information is not shared with other users." +msgstr "此資訊不會分享給其他用戶。" + +#: src/view/com/modals/VerifyEmail.tsx:127 +msgid "This is important in case you ever need to change your email or reset your password." +msgstr "這很重要,以防您將來需要更改電子郵件地址或重設密碼。" + +#: src/components/moderation/ModerationDetailsDialog.tsx:127 +msgid "This label was applied by <0>{0}." +msgstr "此標記由 <0>{0} 新增。" + +#: src/components/moderation/ModerationDetailsDialog.tsx:125 +msgid "This label was applied by the author." +msgstr "此標記由發布者新增。" + +#: src/components/moderation/LabelsOnMeDialog.tsx:166 +msgid "This label was applied by you." +msgstr "此標記由您新增。" + +#: src/screens/Profile/Sections/Labels.tsx:188 +msgid "This labeler hasn't declared what labels it publishes, and may not be active." +msgstr "此標記者尚未宣告它發佈的標記,而且可能不會生效。" + +#: src/view/com/modals/LinkWarning.tsx:72 +msgid "This link is taking you to the following website:" +msgstr "此連結將帶您到以下網站:" + +#: src/view/screens/ProfileList.tsx:907 +msgid "This list is empty!" +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 "此內容管理服務暫時無法使用,詳情請見下文。如果問題持續存在,請與我們聯絡。" + +#: src/view/com/modals/AddAppPasswords.tsx:110 +msgid "This name is already in use" +msgstr "此名稱已被使用" + +#: src/view/com/post-thread/PostThreadItem.tsx:135 +msgid "This post has been deleted." +msgstr "這則貼文已被刪除。" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:458 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:313 +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:440 +msgid "This post will be hidden from feeds." +msgstr "這則貼文將從動態隱藏。" + +#: src/view/com/profile/ProfileMenu.tsx:375 +msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't logged in." +msgstr "只有登入用戶能見到此個人檔案。 未登入的人將看不到它。" + +#: src/screens/Signup/StepInfo/Policies.tsx:37 +msgid "This service has not provided terms of service or a privacy policy." +msgstr "此服務尚未提供服務條款或隱私政策。" + +#: src/view/com/modals/ChangeHandle.tsx:439 +msgid "This should create a domain record at:" +msgstr "這應該會在以下位置建立一個域名記錄:" + +#: src/view/com/profile/ProfileFollowers.tsx:87 +msgid "This user doesn't have any followers." +msgstr "此用戶沒有任何追隨者。" + +#: src/components/dms/MessagesListBlockedFooter.tsx:60 +msgid "This user has blocked you" +msgstr "這個用戶已封鎖您" + +#: src/components/moderation/ModerationDetailsDialog.tsx:72 +#: src/lib/moderation/useModerationCauseDescription.ts:70 +msgid "This user has blocked you. You cannot view their content." +msgstr "此用戶已封鎖您,您無法查看他們的內容。" + +#: src/lib/moderation/useGlobalLabelStrings.ts:30 +msgid "This user has requested that their content only be shown to signed-in users." +msgstr "此用戶要求僅將其內容顯示給已登入的用戶。" + +#: src/components/moderation/ModerationDetailsDialog.tsx:55 +msgid "This user is included in the <0>{0} list which you have blocked." +msgstr "此用戶包含在您已封鎖的 <0>{0} 列表中。" + +#: src/components/moderation/ModerationDetailsDialog.tsx:84 +msgid "This user is included in the <0>{0} list which you have muted." +msgstr "此用戶包含在您已靜音的 <0>{0} 列表中。" + +#: src/components/NewskieDialog.tsx:50 +msgid "This user is new here. Press for more info about when they joined." +msgstr "該用戶是新來帳號,請按此了解更多有關他們何時加入的資訊。" + +#: src/view/com/profile/ProfileFollows.tsx:87 +msgid "This user isn't following anyone." +msgstr "此用戶未跟隨任何人。" + +#: src/components/dialogs/MutedWords.tsx:283 +msgid "This will delete {0} from your muted words. You can always add it back later." +msgstr "這將從您的靜音文字中刪除 {0},您隨時可以新增回來。" + +#: src/view/screens/Settings/index.tsx:594 +msgid "Thread preferences" +msgstr "討論串偏好" + +#: src/view/screens/PreferencesThreads.tsx:53 +#: src/view/screens/Settings/index.tsx:604 +msgid "Thread Preferences" +msgstr "討論串偏好" + +#: src/view/screens/PreferencesThreads.tsx:119 +msgid "Threaded Mode" +msgstr "樹狀顯示模式" + +#: src/Navigation.tsx:282 +msgid "Threads Preferences" +msgstr "討論串偏好" + +#: src/view/screens/Settings/DisableEmail2FADialog.tsx:102 +msgid "To disable the email 2FA method, please verify your access to the email address." +msgstr "若要關閉電子郵件雙重驗證,請驗證您的電子郵件地址。" + +#: src/components/dms/ReportConversationPrompt.tsx:20 +msgid "To report a conversation, please report one of its messages via the conversation screen. This lets our moderators understand the context of your issue." +msgstr "若要檢舉對話,請透過對話畫面檢舉其中一則訊息。這可以讓我們的內容管理者瞭解問題的來龍去脈。" + +#: src/components/ReportDialog/SelectLabelerView.tsx:33 +msgid "To whom would you like to send this report?" +msgstr "您希望向誰提交此檢舉?" + +#: src/components/dialogs/MutedWords.tsx:112 +msgid "Toggle between muted word options." +msgstr "在靜音文字選項之間切換。" + +#: src/view/com/util/forms/DropdownButton.tsx:255 +msgid "Toggle dropdown" +msgstr "切換下拉式選單" + +#: src/screens/Moderation/index.tsx:332 +msgid "Toggle to enable or disable adult content" +msgstr "切換以啟用或停用成人內容" + +#: src/screens/Hashtag.tsx:88 +#: src/view/screens/Search/Search.tsx:349 +msgid "Top" +msgstr "熱門" + +#: src/view/com/modals/EditImage.tsx:272 +msgid "Transformations" +msgstr "轉換" + +#: src/components/dms/MessageMenu.tsx:103 +#: src/components/dms/MessageMenu.tsx:105 +#: src/view/com/post-thread/PostThreadItem.tsx:681 +#: src/view/com/post-thread/PostThreadItem.tsx:683 +#: src/view/com/util/forms/PostDropdownBtn.tsx:277 +#: src/view/com/util/forms/PostDropdownBtn.tsx:279 +msgid "Translate" +msgstr "翻譯" + +#: src/view/com/util/error/ErrorScreen.tsx:82 +msgctxt "action" +msgid "Try again" +msgstr "重試" + +#: src/view/screens/Settings/index.tsx:745 +msgid "Two-factor authentication" +msgstr "雙重驗證" + +#: src/screens/Messages/Conversation/MessageInput.tsx:139 +msgid "Type your message here" +msgstr "在此輸入訊息" + +#: src/view/com/modals/ChangeHandle.tsx:422 +msgid "Type:" +msgstr "類型:" + +#: src/view/screens/ProfileList.tsx:535 +msgid "Un-block list" +msgstr "取消封鎖列表" + +#: src/view/screens/ProfileList.tsx:520 +msgid "Un-mute list" +msgstr "取消靜音列表" + +#: src/screens/Login/ForgotPasswordForm.tsx:74 +#: src/screens/Login/index.tsx:78 +#: src/screens/Login/LoginForm.tsx:142 +#: src/screens/Login/SetNewPasswordForm.tsx:77 +#: src/screens/Signup/index.tsx:66 +#: src/view/com/modals/ChangePassword.tsx:71 +msgid "Unable to contact your service. Please check your Internet connection." +msgstr "無法連線到服務,請檢查您的網路連線。" + +#: src/components/dms/MessagesListBlockedFooter.tsx:89 +#: src/components/dms/MessagesListBlockedFooter.tsx:96 +#: src/components/dms/MessagesListBlockedFooter.tsx:104 +#: src/components/dms/MessagesListBlockedFooter.tsx:111 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:192 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:314 +#: src/view/com/profile/ProfileMenu.tsx:366 +#: src/view/screens/ProfileList.tsx:626 +msgid "Unblock" +msgstr "解除封鎖" + +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:197 +msgctxt "action" +msgid "Unblock" +msgstr "解除封鎖" + +#: src/components/dms/ConvoMenu.tsx:188 +#: src/components/dms/ConvoMenu.tsx:192 +msgid "Unblock account" +msgstr "解除封鎖帳號" + +#: src/view/com/profile/ProfileMenu.tsx:304 +#: src/view/com/profile/ProfileMenu.tsx:310 +msgid "Unblock Account" +msgstr "解除封鎖帳號" + +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:308 +#: src/view/com/profile/ProfileMenu.tsx:348 +msgid "Unblock Account?" +msgstr "解除封鎖?" + +#: src/view/com/util/post-ctrls/RepostButton.tsx:64 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:70 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:74 +msgid "Undo repost" +msgstr "取消轉貼" + +#: src/view/com/profile/FollowButton.tsx:60 +msgctxt "action" +msgid "Unfollow" +msgstr "取消跟隨" + +#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:247 +msgid "Unfollow" +msgstr "取消跟隨" + +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:237 +msgid "Unfollow {0}" +msgstr "取消跟隨 {0}" + +#: src/view/com/profile/ProfileMenu.tsx:246 +#: src/view/com/profile/ProfileMenu.tsx:256 +msgid "Unfollow Account" +msgstr "取消跟隨" + +#: src/view/screens/ProfileFeed.tsx:570 +msgid "Unlike this feed" +msgstr "取消喜歡這個動態源" + +#: src/components/TagMenu/index.tsx:249 +#: src/view/screens/ProfileList.tsx:633 +msgid "Unmute" +msgstr "取消靜音" + +#: src/components/TagMenu/index.web.tsx:104 +msgid "Unmute {truncatedTag}" +msgstr "取消靜音 {truncatedTag}" + +#: src/view/com/profile/ProfileMenu.tsx:283 +#: src/view/com/profile/ProfileMenu.tsx:289 +msgid "Unmute Account" +msgstr "取消靜音帳號" + +#: src/components/TagMenu/index.tsx:208 +msgid "Unmute all {displayTag} posts" +msgstr "取消對所有 {displayTag} 貼文的靜音" + +#: src/components/dms/ConvoMenu.tsx:176 +msgid "Unmute conversation" +msgstr "取消靜音對話" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:362 +#: src/view/com/util/forms/PostDropdownBtn.tsx:367 +msgid "Unmute thread" +msgstr "取消靜音討論串" + +#: src/view/screens/ProfileFeed.tsx:290 +#: src/view/screens/ProfileList.tsx:617 +msgid "Unpin" +msgstr "取消釘選" + +#: src/view/screens/ProfileFeed.tsx:287 +msgid "Unpin from home" +msgstr "自首頁取消釘選" + +#: src/view/screens/ProfileList.tsx:500 +msgid "Unpin moderation list" +msgstr "取消釘選內容管理列表" + +#: src/view/screens/ProfileList.tsx:290 +msgid "Unpinned from your feeds" +msgstr "已從您的動態源取消釘選" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:228 +msgid "Unsubscribe" +msgstr "取消訂閱" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:193 +msgid "Unsubscribe from this labeler" +msgstr "取消訂閱這個標記者" + +#: src/lib/moderation/useReportOptions.ts:71 +#: src/lib/moderation/useReportOptions.ts:84 +msgid "Unwanted Sexual Content" +msgstr "不受歡迎的色情內容" + +#: src/view/com/modals/UserAddRemoveLists.tsx:83 +msgid "Update {displayName} in Lists" +msgstr "更新列表中的 {displayName}" + +#: src/view/com/modals/ChangeHandle.tsx:502 +msgid "Update to {handle}" +msgstr "更新至 {handle}" + +#: src/screens/Login/SetNewPasswordForm.tsx:186 +msgid "Updating..." +msgstr "更新中…" + +#: src/screens/Onboarding/StepProfile/index.tsx:281 +msgid "Upload a photo instead" +msgstr "或是上傳圖片" + +#: src/view/com/modals/ChangeHandle.tsx:448 +msgid "Upload a text file to:" +msgstr "上傳文字檔案至:" + +#: src/view/com/util/UserAvatar.tsx:352 +#: src/view/com/util/UserAvatar.tsx:355 +#: src/view/com/util/UserBanner.tsx:123 +#: src/view/com/util/UserBanner.tsx:126 +msgid "Upload from Camera" +msgstr "從相機上傳" + +#: src/view/com/util/UserAvatar.tsx:369 +#: src/view/com/util/UserBanner.tsx:140 +msgid "Upload from Files" +msgstr "從檔案上傳" + +#: src/view/com/util/UserAvatar.tsx:363 +#: src/view/com/util/UserAvatar.tsx:367 +#: src/view/com/util/UserBanner.tsx:134 +#: src/view/com/util/UserBanner.tsx:138 +msgid "Upload from Library" +msgstr "從圖片庫上傳" + +#: src/view/com/modals/ChangeHandle.tsx:402 +msgid "Use a file on your server" +msgstr "使用您伺服器上的檔案" + +#: src/view/screens/AppPasswords.tsx:200 +msgid "Use app passwords to login to other Bluesky clients without giving full access to your account or password." +msgstr "使用應用程式專用密碼登入到其他 Bluesky 客戶端,而無需提供完整的帳號權限和密碼。" + +#: src/view/com/modals/ChangeHandle.tsx:513 +msgid "Use bsky.social as hosting provider" +msgstr "使用 bsky.social 作為託管服務供應商" + +#: src/view/com/modals/ChangeHandle.tsx:512 +msgid "Use default provider" +msgstr "使用預設託管服務供應商" + +#: src/view/com/modals/InAppBrowserConsent.tsx:56 +#: src/view/com/modals/InAppBrowserConsent.tsx:58 +msgid "Use in-app browser" +msgstr "使用內建瀏覽器" + +#: src/view/com/modals/InAppBrowserConsent.tsx:66 +#: src/view/com/modals/InAppBrowserConsent.tsx:68 +msgid "Use my default browser" +msgstr "使用我的預設瀏覽器" + +#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:53 +msgid "Use recommended" +msgstr "使用推薦" + +#: src/view/com/modals/ChangeHandle.tsx:394 +msgid "Use the DNS panel" +msgstr "使用 DNS 控制台" + +#: src/view/com/modals/AddAppPasswords.tsx:205 +msgid "Use this to sign into the other app along with your handle." +msgstr "使用這個和您的帳號代碼一起登入其他應用程式。" + +#: src/view/com/modals/InviteCodes.tsx:201 +msgid "Used by:" +msgstr "使用者:" + +#: src/components/moderation/ModerationDetailsDialog.tsx:64 +#: src/lib/moderation/useModerationCauseDescription.ts:58 +msgid "User Blocked" +msgstr "用戶被封鎖" + +#: src/lib/moderation/useModerationCauseDescription.ts:50 +msgid "User Blocked by \"{0}\"" +msgstr "用戶被「{0}」封鎖" + +#: src/components/dms/BlockedByListDialog.tsx:27 +msgid "User blocked by list" +msgstr "用戶已被列表封鎖" + +#: src/components/moderation/ModerationDetailsDialog.tsx:53 +msgid "User Blocked by List" +msgstr "用戶被列表封鎖" + +#: src/lib/moderation/useModerationCauseDescription.ts:68 +msgid "User Blocking You" +msgstr "用戶封鎖了您" + +#: src/components/moderation/ModerationDetailsDialog.tsx:70 +msgid "User Blocks You" +msgstr "用戶封鎖了您" + +#: src/view/com/lists/ListCard.tsx:87 +#: src/view/com/modals/UserAddRemoveLists.tsx:209 +msgid "User list by {0}" +msgstr "{0} 的用戶列表" + +#: src/view/screens/ProfileList.tsx:831 +msgid "User list by <0/>" +msgstr "<0/> 的用戶列表" + +#: src/view/com/lists/ListCard.tsx:85 +#: src/view/com/modals/UserAddRemoveLists.tsx:207 +#: src/view/screens/ProfileList.tsx:829 +msgid "User list by you" +msgstr "您的用戶列表" + +#: src/view/com/modals/CreateOrEditList.tsx:184 +msgid "User list created" +msgstr "已建立用戶列表" + +#: src/view/com/modals/CreateOrEditList.tsx:170 +msgid "User list updated" +msgstr "已更新用戶列表" + +#: src/view/screens/Lists.tsx:63 +msgid "User Lists" +msgstr "用戶列表" + +#: src/screens/Login/LoginForm.tsx:174 +msgid "Username or email address" +msgstr "帳號代碼或電子郵件地址" + +#: src/view/screens/ProfileList.tsx:865 +msgid "Users" +msgstr "用戶" + +#: src/view/com/threadgate/WhoCanReply.tsx:274 +msgid "users followed by <0/>" +msgstr "被 <0/> 跟隨的用戶" + +#: src/components/dms/MessagesNUX.tsx:140 +#: src/components/dms/MessagesNUX.tsx:143 +#: src/screens/Messages/Settings.tsx:84 +#: src/screens/Messages/Settings.tsx:87 +msgid "Users I follow" +msgstr "我跟隨的用戶" + +#: src/view/com/modals/Threadgate.tsx:109 +msgid "Users in \"{0}\"" +msgstr "「{0}」中的用戶" + +#: src/components/LikesDialog.tsx:85 +msgid "Users that have liked this content or profile" +msgstr "喜歡此內容或個人檔案的用戶" + +#: src/view/com/modals/ChangeHandle.tsx:430 +msgid "Value:" +msgstr "值:" + +#: src/view/com/modals/ChangeHandle.tsx:504 +msgid "Verify DNS Record" +msgstr "驗證 DNS 紀錄" + +#: src/view/screens/Settings/index.tsx:982 +msgid "Verify email" +msgstr "驗證電子郵件" + +#: src/view/screens/Settings/index.tsx:1007 +msgid "Verify my email" +msgstr "驗證我的電子郵件" + +#: src/view/screens/Settings/index.tsx:1016 +msgid "Verify My Email" +msgstr "驗證我的電子郵件" + +#: src/view/com/modals/ChangeEmail.tsx:200 +#: src/view/com/modals/ChangeEmail.tsx:202 +msgid "Verify New Email" +msgstr "驗證新的電子郵件" + +#: src/view/com/modals/ChangeHandle.tsx:505 +msgid "Verify Text File" +msgstr "驗證文字檔案" + +#: src/view/com/modals/VerifyEmail.tsx:111 +msgid "Verify Your Email" +msgstr "驗證您的電子郵件" + +#: src/view/screens/Settings/index.tsx:935 +msgid "Version {appVersion} {bundleInfo}" +msgstr "版本 {appVersion} {bundleInfo}" + +#: src/screens/Onboarding/index.tsx:39 +msgid "Video Games" +msgstr "電子遊戲" + +#: src/screens/Profile/Header/Shell.tsx:113 +msgid "View {0}'s avatar" +msgstr "查看 {0} 的頭像" + +#: src/view/com/notifications/FeedItem.tsx:215 +msgid "View {0}'s profile" +msgstr "查看 {0} 的個人檔案" + +#: src/components/ProfileHoverCard/index.web.tsx:430 +msgid "View blocked user's profile" +msgstr "查看已封鎖用戶的個人檔案" + +#: src/view/screens/Log.tsx:56 +msgid "View debug entry" +msgstr "查看偵錯項目" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:136 +msgid "View details" +msgstr "查看詳細資訊" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:131 +msgid "View details for reporting a copyright violation" +msgstr "查看詳細資訊以檢舉侵犯版權" + +#: src/view/com/posts/FeedSlice.tsx:124 +msgid "View full thread" +msgstr "查看整個討論串" + +#: src/components/moderation/LabelsOnMe.tsx:48 +msgid "View information about these labels" +msgstr "查看有關這些標記的資訊" + +#: src/components/ProfileHoverCard/index.web.tsx:418 +#: src/components/ProfileHoverCard/index.web.tsx:436 +#: src/components/ProfileHoverCard/index.web.tsx:463 +#: src/view/com/posts/AviFollowButton.tsx:58 +#: src/view/com/posts/FeedErrorMessage.tsx:174 +msgid "View profile" +msgstr "查看資料" + +#: src/view/com/profile/ProfileSubpageHeader.tsx:127 +msgid "View the avatar" +msgstr "查看頭像" + +#: src/components/LabelingServiceCard/index.tsx:137 +msgid "View the labeling service provided by @{0}" +msgstr "查看由 @{0} 提供的標記服務" + +#: src/view/screens/ProfileFeed.tsx:582 +msgid "View users who like this feed" +msgstr "查看喜歡此動態源的用戶" + +#: src/view/com/home/HomeHeaderLayout.web.tsx:79 +#: src/view/com/home/HomeHeaderLayoutMobile.tsx:84 +msgid "View your feeds and explore more" +msgstr "查看您的動態並探索更多內容" + +#: src/view/com/modals/LinkWarning.tsx:89 +#: src/view/com/modals/LinkWarning.tsx:95 +msgid "Visit Site" +msgstr "造訪網站" + +#: src/components/moderation/LabelPreference.tsx:135 +#: src/lib/moderation/useLabelBehaviorDescription.ts:17 +#: src/lib/moderation/useLabelBehaviorDescription.ts:22 +msgid "Warn" +msgstr "警告" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:48 +msgid "Warn content" +msgstr "警告內容" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:46 +msgid "Warn content and filter from feeds" +msgstr "警告內容並從動態源中過濾" + +#: src/screens/Hashtag.tsx:210 +msgid "We couldn't find any results for that hashtag." +msgstr "我們找不到任何與該標籤相關的結果。" + +#: src/screens/Messages/Conversation/index.tsx:107 +msgid "We couldn't load this conversation" +msgstr "我們無法載入這個對話" + +#: src/screens/SignupQueued.tsx:139 +msgid "We estimate {estimatedTime} until your account is ready." +msgstr "我們估計還需要 {estimatedTime} 才能準備好您的帳號。" + +#: src/screens/Onboarding/StepFinished.tsx:126 +msgid "We hope you have a wonderful time. Remember, Bluesky is:" +msgstr "我們希望您在此度過愉快的時光。請記住,Bluesky 是:" + +#: src/view/com/posts/DiscoverFallbackHeader.tsx:29 +msgid "We ran out of posts from your follows. Here's the latest from <0/>." +msgstr "您已看完了您跟隨的貼文。這是來自 <0/> 的最新貼文。" + +#: src/components/dialogs/MutedWords.tsx:203 +msgid "We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." +msgstr "我們建議避免新增在許多貼文中常用的文字,因為這可能令您看不到任何貼文。" + +#: src/components/dialogs/BirthDateSettings.tsx:52 +msgid "We were unable to load your birth date preferences. Please try again." +msgstr "我們無法載入您的出生日期偏好,請再試一次。" + +#: src/screens/Moderation/index.tsx:385 +msgid "We were unable to load your configured labelers at this time." +msgstr "我們目前無法載入您已設定的標記者。" + +#: src/screens/Onboarding/StepInterests/index.tsx:138 +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." +msgstr "我們無法連線到網際網路,請重試以繼續設定您的帳號。如果仍繼續失敗,您可以選擇跳過此流程。" + +#: src/screens/SignupQueued.tsx:143 +msgid "We will let you know when your account is ready." +msgstr "我們會在您的帳號準備好時通知您。" + +#: src/screens/Onboarding/StepInterests/index.tsx:143 +msgid "We'll use this to help customize your experience." +msgstr "我們將使用這些資訊來協助訂製您的體驗。" + +#: src/components/dms/dialogs/SearchablePeopleList.tsx:90 +msgid "We're having network issues, try again" +msgstr "我們遇到網路問題,請重試" + +#: src/screens/Signup/index.tsx:142 +msgid "We're so excited to have you join us!" +msgstr "我們非常高興您加入我們!" + +#: src/view/screens/ProfileList.tsx:91 +msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." +msgstr "很抱歉,我們無法解析此列表。如果問題持續發生,請聯繫列表建立者 @{handleOrDid}。" + +#: src/components/dialogs/MutedWords.tsx:229 +msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." +msgstr "很抱歉,我們目前無法載入您的靜音文字。請稍後再試。" + +#: src/view/screens/Search/Search.tsx:206 +msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." +msgstr "很抱歉,無法完成您的搜尋請求。請稍後再試。" + +#: src/view/com/composer/Composer.tsx:318 +msgid "We're sorry! The post you are replying to has been deleted." +msgstr "很抱歉!您回覆的貼文已被刪除。" + +#: src/components/Lists.tsx:212 +#: src/view/screens/NotFound.tsx:48 +msgid "We're sorry! We can't find the page you were looking for." +msgstr "很抱歉!我們找不到您正在尋找的頁面。" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:330 +msgid "We're sorry! You can only subscribe to twenty labelers, and you've reached your limit of twenty." +msgstr "抱歉!您只能訂閱二十個標記者,您已達到二十個的限制。" + +#: src/screens/Deactivated.tsx:128 +msgid "Welcome back!" +msgstr "歡迎回來!" + +#: src/screens/Onboarding/StepInterests/index.tsx:135 +msgid "What are your interests?" +msgstr "您感興趣的是什麼?" + +#: src/view/com/auth/SplashScreen.tsx:40 +#: src/view/com/auth/SplashScreen.web.tsx:86 +#: src/view/com/composer/Composer.tsx:359 +msgid "What's up?" +msgstr "發生了什麼新鮮事?" + +#: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:78 +msgid "Which languages are used in this post?" +msgstr "這個貼文使用了哪些語言?" + +#: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:77 +msgid "Which languages would you like to see in your algorithmic feeds?" +msgstr "您想在演算法動態源中看到哪些語言?" + +#: src/components/dms/MessagesNUX.tsx:110 +#: src/components/dms/MessagesNUX.tsx:124 +msgid "Who can message you?" +msgstr "誰可以傳送訊息給您?" + +#: src/view/com/modals/Threadgate.tsx:69 +#: src/view/com/threadgate/WhoCanReply.tsx:73 +#: src/view/com/threadgate/WhoCanReply.tsx:130 +msgid "Who can reply" +msgstr "誰可以回覆" + +#: src/view/com/threadgate/WhoCanReply.tsx:206 +msgid "Who can reply dialog" +msgstr "「誰可以回覆」對話窗" + +#: src/view/com/threadgate/WhoCanReply.tsx:210 +msgid "Who can reply?" +msgstr "誰可以回覆?" + +#: src/screens/Home/NoFeedsPinned.tsx:79 +#: src/screens/Messages/List/index.tsx:185 +msgid "Whoops!" +msgstr "哎呀!" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:44 +msgid "Why should this content be reviewed?" +msgstr "為什麼應該審查這個內容?" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:57 +msgid "Why should this feed be reviewed?" +msgstr "為什麼應該審查這個動態源?" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:54 +msgid "Why should this list be reviewed?" +msgstr "為什麼應該審查這個列表?" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:60 +msgid "Why should this message be reviewed?" +msgstr "為什麼應該審查這則訊息?" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:51 +msgid "Why should this post be reviewed?" +msgstr "為什麼應該審查這則貼文?" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:48 +msgid "Why should this user be reviewed?" +msgstr "為什麼應該審查這個用戶?" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:125 +msgid "Wide" +msgstr "寬" + +#: src/screens/Messages/Conversation/MessageInput.tsx:140 +#: src/screens/Messages/Conversation/MessageInput.web.tsx:134 +msgid "Write a message" +msgstr "撰寫訊息" + +#: src/view/com/composer/Composer.tsx:551 +msgid "Write post" +msgstr "撰寫貼文" + +#: src/view/com/composer/Composer.tsx:358 +#: src/view/com/composer/Prompt.tsx:39 +msgid "Write your reply" +msgstr "撰寫您的回覆" + +#: src/screens/Onboarding/index.tsx:25 +msgid "Writers" +msgstr "作家" + +#: src/view/com/composer/select-language/SuggestedLanguage.tsx:77 +#: src/view/screens/PreferencesFollowingFeed.tsx:128 +#: src/view/screens/PreferencesFollowingFeed.tsx:200 +#: src/view/screens/PreferencesFollowingFeed.tsx:235 +#: src/view/screens/PreferencesFollowingFeed.tsx:270 +#: src/view/screens/PreferencesThreads.tsx:106 +#: src/view/screens/PreferencesThreads.tsx:129 +msgid "Yes" +msgstr "開" + +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:106 +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:108 +msgid "Yes, deactivate" +msgstr "確定並停用" + +#: src/screens/Deactivated.tsx:150 +msgid "Yes, reactivate my account" +msgstr "確定並停用我的帳號" + +#: src/components/dms/MessageItem.tsx:188 +msgid "Yesterday, {time}" +msgstr "昨天,{time}" + +#: src/screens/SignupQueued.tsx:136 +msgid "You are in line." +msgstr "你正處於隊列之中。" + +#: src/view/com/profile/ProfileFollows.tsx:86 +msgid "You are not following anyone." +msgstr "您沒有跟隨任何人。" + +#: src/view/com/posts/FollowingEmptyState.tsx:63 +#: src/view/com/posts/FollowingEndOfFeed.tsx:64 +msgid "You can also discover new Custom Feeds to follow." +msgstr "您也可以探索並跟隨新的自訂動態源。" + +#: src/view/com/modals/DeleteAccount.tsx:202 +msgid "You can also temporarily deactivate your account instead, and reactivate it at any time." +msgstr "您也可以暫時停用帳號,然後隨時重新啟用。" + +#: src/components/dms/MessagesNUX.tsx:119 +msgid "You can change this at any time." +msgstr "您可以隨時變更該設定。" + +#: src/screens/Messages/Settings.tsx:111 +msgid "You can continue ongoing conversations regardless of which setting you choose." +msgstr "無論選擇哪種設定,都不會影響已發起的對話。" + +#: src/screens/Login/index.tsx:158 +#: src/screens/Login/PasswordUpdatedForm.tsx:33 +msgid "You can now sign in with your new password." +msgstr "您現在可以使用新密碼登入。" + +#: src/screens/Deactivated.tsx:136 +msgid "You can reactivate your account to continue logging in. Your profile and posts will be visible to other users." +msgstr "您可以登入以重新啟用帳號。其他用戶將可以重新看到您的個人檔案和貼文。" + +#: src/view/com/profile/ProfileFollowers.tsx:86 +msgid "You do not have any followers." +msgstr "您沒有任何跟隨者。" + +#: src/screens/Profile/KnownFollowers.tsx:99 +msgid "You don't follow any users who follow @{name}." +msgstr "您沒有跟隨任何也跟隨 @{name} 之用戶。" + +#: 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." +msgstr "您目前還沒有邀請碼!當您持續使用 Bluesky 一段時間後,我們將提供一些新的邀請碼給您。" + +#: src/view/screens/SavedFeeds.tsx:117 +msgid "You don't have any pinned feeds." +msgstr "您目前還沒有任何釘選的動態源。" + +#: src/view/screens/SavedFeeds.tsx:158 +msgid "You don't have any saved feeds." +msgstr "您目前還沒有任何已儲存的動態源。" + +#: src/view/com/post-thread/PostThread.tsx:195 +msgid "You have blocked the author or you have been blocked by the author." +msgstr "您已封鎖該作者,或您已被該作者封鎖。" + +#: src/components/dms/MessagesListBlockedFooter.tsx:58 +msgid "You have blocked this user" +msgstr "您已封鎖該用戶" + +#: src/components/moderation/ModerationDetailsDialog.tsx:66 +#: src/lib/moderation/useModerationCauseDescription.ts:52 +#: src/lib/moderation/useModerationCauseDescription.ts:60 +msgid "You have blocked this user. You cannot view their content." +msgstr "您已封鎖了此用戶,您將無法查看他們發佈的內容。" + +#: src/screens/Login/SetNewPasswordForm.tsx:54 +#: src/screens/Login/SetNewPasswordForm.tsx:91 +#: src/view/com/modals/ChangePassword.tsx:88 +#: src/view/com/modals/ChangePassword.tsx:122 +msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." +msgstr "您輸入的邀請碼無效。它應該長得像這樣 XXXXX-XXXXX。" + +#: src/lib/moderation/useModerationCauseDescription.ts:111 +msgid "You have hidden this post" +msgstr "您已隱藏這則貼文" + +#: src/components/moderation/ModerationDetailsDialog.tsx:101 +msgid "You have hidden this post." +msgstr "您已隱藏這則貼文。" + +#: src/components/moderation/ModerationDetailsDialog.tsx:94 +#: src/lib/moderation/useModerationCauseDescription.ts:94 +msgid "You have muted this account." +msgstr "您已隱藏這個帳號。" + +#: src/lib/moderation/useModerationCauseDescription.ts:88 +msgid "You have muted this user" +msgstr "您已靜音這個用戶" + +#: src/screens/Messages/List/index.tsx:225 +msgid "You have no conversations yet. Start one!" +msgstr "您還沒有對話,與其他用戶開始對話吧!" + +#: src/view/com/feeds/ProfileFeedgens.tsx:141 +msgid "You have no feeds." +msgstr "您沒有建立任何動態源。" + +#: src/view/com/lists/MyLists.tsx:90 +#: src/view/com/lists/ProfileLists.tsx:145 +msgid "You have no lists." +msgstr "您沒有建立任何列表。" + +#: src/view/screens/ModerationBlockedAccounts.tsx:134 +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/AppPasswords.tsx:91 +msgid "You have not created any app passwords yet. You can create one by pressing the button below." +msgstr "您還沒有建立任何應用程式專用密碼,如您想建立一個,按下面的按鈕。" + +#: src/view/screens/ModerationMutedAccounts.tsx:133 +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/components/Lists.tsx:52 +msgid "You have reached the end" +msgstr "已經到底部啦!" + +#: src/components/dialogs/MutedWords.tsx:249 +msgid "You haven't muted any words or tags yet" +msgstr "您還沒有隱藏任何文字或標籤" + +#: src/components/moderation/LabelsOnMeDialog.tsx:86 +msgid "You may appeal non-self labels if you feel they were placed in error." +msgstr "如果您認為這些標記有誤,且標記並非由您新增,您可以提出申訴。" + +#: src/components/moderation/LabelsOnMeDialog.tsx:91 +msgid "You may appeal these labels if you feel they were placed in error." +msgstr "如果您覺得這些標記有誤,您可以提出申訴。" + +#: src/screens/Signup/StepInfo/Policies.tsx:79 +msgid "You must be 13 years of age or older to sign up." +msgstr "您必須年滿 13 歲才能註冊。" + +#: src/components/ReportDialog/SubmitView.tsx:205 +msgid "You must select at least one labeler for a report" +msgstr "您必須選擇至少一個標記者來提交檢舉" + +#: src/screens/Deactivated.tsx:131 +msgid "You previously deactivated @{0}." +msgstr "您之前停用了 @{0}。" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:174 +msgid "You will no longer receive notifications for this thread" +msgstr "您將不再收到這條討論串的通知" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:170 +msgid "You will now receive notifications for this thread" +msgstr "您將收到這條討論串的通知" + +#: src/screens/Login/SetNewPasswordForm.tsx:104 +msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password." +msgstr "您將收到一封包含重設碼的電子郵件。請在此輸入該「重設碼」,然後輸入您的新密碼。" + +#: src/screens/Messages/List/ChatListItem.tsx:114 +msgid "You: {0}" +msgstr "您:{0}" + +#: src/screens/Messages/List/ChatListItem.tsx:143 +msgid "You: {defaultEmbeddedContentMessage}" +msgstr "您:{defaultEmbeddedContentMessage}" + +#: src/screens/Messages/List/ChatListItem.tsx:136 +msgid "You: {short}" +msgstr "您:{short}" + +#: src/screens/SignupQueued.tsx:93 +#: src/screens/SignupQueued.tsx:94 +#: src/screens/SignupQueued.tsx:109 +msgid "You're in line" +msgstr "輪到您了" + +#: src/screens/Deactivated.tsx:89 +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:54 +msgid "You're logged in with an App Password. Please log in with your main password to continue deactivating your account." +msgstr "您正在使用應用程式專用密碼登入。請使用您的主密碼登入,以繼續停用您的帳號。" + +#: src/screens/Onboarding/StepFinished.tsx:123 +msgid "You're ready to go!" +msgstr "您已完成設定!" + +#: src/components/moderation/ModerationDetailsDialog.tsx:98 +#: src/lib/moderation/useModerationCauseDescription.ts:103 +msgid "You've chosen to hide a word or tag within this post." +msgstr "您選擇在這則貼文中隱藏文字或標籤。" + +#: src/view/com/posts/FollowingEndOfFeed.tsx:44 +msgid "You've reached the end of your feed! Find some more accounts to follow." +msgstr "您已經瀏覽完貼文啦!跟隨其他帳號吧。" + +#: src/screens/Signup/index.tsx:164 +msgid "Your account" +msgstr "您的帳號" + +#: src/view/com/modals/DeleteAccount.tsx:88 +msgid "Your account has been deleted" +msgstr "您的帳號已刪除" + +#: src/view/screens/Settings/ExportCarDialog.tsx:65 +msgid "Your account repository, containing all public data records, can be downloaded as a \"CAR\" file. This file does not include media embeds, such as images, or your private data, which must be fetched separately." +msgstr "您可以將您的帳號存放庫下載為一個「CAR」檔案。該檔案包含了所有公開的資料紀錄,但不包括嵌入媒體,例如圖片或您的私人資料,目前這些資料必須另外擷取。" + +#: src/screens/Signup/StepInfo/index.tsx:123 +msgid "Your birth date" +msgstr "您的生日" + +#: src/screens/Messages/Conversation/ChatDisabled.tsx:25 +msgid "Your chats have been disabled" +msgstr "您的對話功能已被停用" + +#: src/view/com/modals/InAppBrowserConsent.tsx:47 +msgid "Your choice will be saved, but can be changed later in settings." +msgstr "您的選擇將被儲存,但可以稍後在設定中更改。" + +#: src/screens/Login/ForgotPasswordForm.tsx:57 +#: src/screens/Signup/state.ts:220 +#: src/view/com/modals/ChangePassword.tsx:55 +msgid "Your email appears to be invalid." +msgstr "您的電子郵件地址似乎無效。" + +#: src/view/com/modals/ChangeEmail.tsx:120 +msgid "Your email has been updated but not verified. As a next step, please verify your new email." +msgstr "您的電子郵件地址已更新但尚未驗證。作為下一步,請驗證您的新電子郵件地址。" + +#: src/view/com/modals/VerifyEmail.tsx:122 +msgid "Your email has not yet been verified. This is an important security step which we recommend." +msgstr "您的電子郵件地址尚未驗證。這是一個我們建議的重要安全步驟。" + +#: src/view/com/posts/FollowingEmptyState.tsx:43 +msgid "Your following feed is empty! Follow more users to see what's happening." +msgstr "您的「Following」動態源是空的!跟隨更多用戶來看看發生了什麼事情。" + +#: src/screens/Signup/StepHandle.tsx:73 +msgid "Your full handle will be" +msgstr "您的完整帳號代碼將修改為" + +#: src/view/com/modals/ChangeHandle.tsx:265 +msgid "Your full handle will be <0>@{0}" +msgstr "您的完整帳號代碼將修改為 <0>@{0}" + +#: src/components/dialogs/MutedWords.tsx:220 +msgid "Your muted words" +msgstr "您的靜音文字" + +#: src/view/com/modals/ChangePassword.tsx:158 +msgid "Your password has been changed successfully!" +msgstr "您的密碼已成功更改!" + +#: src/view/com/composer/Composer.tsx:349 +msgid "Your post has been published" +msgstr "您的貼文已發佈" + +#: src/screens/Onboarding/StepFinished.tsx:138 +msgid "Your posts, likes, and blocks are public. Mutes are private." +msgstr "您的貼文、按喜歡和封鎖是公開可見的,而靜音是私人的。" + +#: src/view/screens/Settings/index.tsx:148 +msgid "Your profile" +msgstr "您的個人檔案" + +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:75 +msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in." +msgstr "其他 Bluesky 用戶將無法再看到您的個人檔案、貼文、動態和列表。您可以隨時登入以重新啟用您的帳號。" + +#: src/view/com/composer/Composer.tsx:348 +msgid "Your reply has been published" +msgstr "您的回覆已發佈" + +#: src/components/dms/ReportDialog.tsx:162 +msgid "Your report will be sent to the Bluesky Moderation Service" +msgstr "您的檢舉將發送至 Bluesky 內容管理服務" + +#: src/screens/Signup/index.tsx:166 +msgid "Your user handle" +msgstr "您的帳號代碼" From eac4668d7312b35721e147e808c181b2be0256bf Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 20 Jun 2024 12:30:48 -0500 Subject: [PATCH 075/127] Merge #4492, fixes profile menu hover (#4580) * Fix button hover color (#4492) * Update ProfileMenu.tsx * Update Button.tsx * Update ProfileFeed.tsx * Update ProfileFeed.tsx * Re-add change post conflict --------- Co-authored-by: Minseo Lee --- src/components/Button.tsx | 2 +- src/view/com/profile/ProfileMenu.tsx | 23 +++++++++++------------ src/view/screens/ProfileFeed.tsx | 13 +++++++------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/components/Button.tsx b/src/components/Button.tsx index deac450eea..54d9eaf3b0 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -231,7 +231,7 @@ export const Button = React.forwardRef( if (!disabled) { baseStyles.push(t.atoms.bg) hoverStyles.push({ - backgroundColor: t.palette.contrast_100, + backgroundColor: t.palette.contrast_25, }) } } diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx index efc2497600..f5e050d707 100644 --- a/src/view/com/profile/ProfileMenu.tsx +++ b/src/view/com/profile/ProfileMenu.tsx @@ -9,7 +9,6 @@ import {useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' import {useAnalytics} from 'lib/analytics/analytics' import {HITSLOP_10} from 'lib/constants' -import {usePalette} from 'lib/hooks/usePalette' import {makeProfileLink} from 'lib/routes/links' import {shareUrl} from 'lib/sharing' import {toShareUrl} from 'lib/strings/url-helpers' @@ -24,7 +23,7 @@ import { import {useSession} from 'state/session' import {EventStopper} from 'view/com/util/EventStopper' import * as Toast from 'view/com/util/Toast' -import {useTheme} from '#/alf' +import {atoms as a, useTheme} from '#/alf' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' import {ListSparkle_Stroke2_Corner0_Rounded as List} from '#/components/icons/ListSparkle' @@ -49,7 +48,7 @@ let ProfileMenu = ({ const {currentAccount, hasSession} = useSession() const t = useTheme() // TODO ALF this - const pal = usePalette('default') + const alf = useTheme() const {track} = useAnalytics() const {openModal} = useModalControls() const reportDialogControl = useReportDialogControl() @@ -187,21 +186,21 @@ let ProfileMenu = ({ - {({props}) => { + {({props, state}) => { return ( - ) From 51f5e6bf900685ef92191f22949d09035733c682 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Thu, 20 Jun 2024 17:45:52 -0400 Subject: [PATCH 076/127] Bsky link card service (#4547) * setup bskycard * quick proof of concept for png card generation * bskycard: use jsx * bskycard: 3x5 profile layout * bskycard: add butterfly overlay * bskycard: tidy * bskycard: separate and reorganize * bskycard: tidy * bskycard: tidy * bskycard: tidy * bskycard: poc of transparent overlay and box shadow * bskycard: reorg impl into src/ directory * bskycard: use more standard app structure * bskycard: setup dockerfile, fix build * bskycard: support for x-origin-verify * bskycard: card layout, filter images based on labels * bskycard: tidy * bskycard: support cluster mode * bskycard: handle error fetching starter pack info * bskycard: tidy * bskycard: fix leak on failed image fetch * bskycard: build workflow * bskyogcard: rename from bskycard * bskyogcard: fix some express plumbing * bskyogcard: add cdn tags, tidy --- .../workflows/build-and-push-ogcard-aws.yaml | 55 + Dockerfile.bskyogcard | 41 + bskyogcard/package.json | 24 + bskyogcard/src/assets/Inter-Bold.ttf | Bin 0 -> 316584 bytes bskyogcard/src/bin.ts | 48 + bskyogcard/src/components/Butterfly.tsx | 16 + bskyogcard/src/components/Img.tsx | 10 + bskyogcard/src/components/StarterPack.tsx | 149 +++ bskyogcard/src/config.ts | 40 + bskyogcard/src/context.ts | 44 + bskyogcard/src/index.ts | 41 + bskyogcard/src/logger.ts | 3 + bskyogcard/src/routes/health.ts | 14 + bskyogcard/src/routes/index.ts | 13 + bskyogcard/src/routes/starter-pack.tsx | 102 ++ bskyogcard/src/routes/util.ts | 36 + bskyogcard/tsconfig.json | 11 + bskyogcard/yarn.lock | 1113 +++++++++++++++++ 18 files changed, 1760 insertions(+) create mode 100644 .github/workflows/build-and-push-ogcard-aws.yaml create mode 100644 Dockerfile.bskyogcard create mode 100644 bskyogcard/package.json create mode 100644 bskyogcard/src/assets/Inter-Bold.ttf create mode 100644 bskyogcard/src/bin.ts create mode 100644 bskyogcard/src/components/Butterfly.tsx create mode 100644 bskyogcard/src/components/Img.tsx create mode 100644 bskyogcard/src/components/StarterPack.tsx create mode 100644 bskyogcard/src/config.ts create mode 100644 bskyogcard/src/context.ts create mode 100644 bskyogcard/src/index.ts create mode 100644 bskyogcard/src/logger.ts create mode 100644 bskyogcard/src/routes/health.ts create mode 100644 bskyogcard/src/routes/index.ts create mode 100644 bskyogcard/src/routes/starter-pack.tsx create mode 100644 bskyogcard/src/routes/util.ts create mode 100644 bskyogcard/tsconfig.json create mode 100644 bskyogcard/yarn.lock diff --git a/.github/workflows/build-and-push-ogcard-aws.yaml b/.github/workflows/build-and-push-ogcard-aws.yaml new file mode 100644 index 0000000000..5d6ff041d3 --- /dev/null +++ b/.github/workflows/build-and-push-ogcard-aws.yaml @@ -0,0 +1,55 @@ +name: build-and-push-ogcard-aws +on: + push: + branches: + - divy/bskycard + +env: + REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} + USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }} + PASSWORD: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_PASSWORD }} + IMAGE_NAME: bskyogcard + +jobs: + ogcard-container-aws: + if: github.repository == 'bluesky-social/social-app' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v1 + + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.USERNAME}} + password: ${{ env.PASSWORD }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=sha,enable=true,priority=100,prefix=,suffix=,format=long + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v4 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + file: ./Dockerfile.bskyogcard + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile.bskyogcard b/Dockerfile.bskyogcard new file mode 100644 index 0000000000..aa68add595 --- /dev/null +++ b/Dockerfile.bskyogcard @@ -0,0 +1,41 @@ +FROM node:20.11-alpine3.18 as build + +# Move files into the image and install +WORKDIR /app + +COPY ./bskyogcard/package.json ./ +COPY ./bskyogcard/yarn.lock ./ +RUN yarn install --frozen-lockfile + +COPY ./bskyogcard ./ + +# build then prune dev deps +RUN yarn build +RUN yarn install --production --ignore-scripts --prefer-offline + +# Uses assets from build stage to reduce build size +FROM node:20.11-alpine3.18 + +RUN apk add --update dumb-init + +# Avoid zombie processes, handle signal forwarding +ENTRYPOINT ["dumb-init", "--"] + +WORKDIR /app +COPY --from=build /app /app +RUN mkdir /app/data && chown node /app/data + +VOLUME /app/data +EXPOSE 3000 +ENV CARD_PORT=3000 +ENV NODE_ENV=production +# potential perf issues w/ io_uring on this version of node +ENV UV_USE_IO_URING=0 + +# https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user +USER node +CMD ["node", "--heapsnapshot-signal=SIGUSR2", "--enable-source-maps", "dist/bin.js"] + +LABEL org.opencontainers.image.source=https://github.com/bluesky-social/social-app +LABEL org.opencontainers.image.description="Bsky Card Service" +LABEL org.opencontainers.image.licenses=UNLICENSED diff --git a/bskyogcard/package.json b/bskyogcard/package.json new file mode 100644 index 0000000000..3be1337fc3 --- /dev/null +++ b/bskyogcard/package.json @@ -0,0 +1,24 @@ +{ + "name": "bskyogcard", + "version": "0.0.0", + "type": "module", + "main": "src/index.ts", + "scripts": { + "start": "node --loader ts-node/esm ./src/bin.ts", + "build": "tsc && cp -r src/assets dist/assets" + }, + "dependencies": { + "@atproto/api": "0.12.19-next.0", + "@atproto/common": "^0.4.0", + "@resvg/resvg-js": "^2.6.2", + "express": "^4.19.2", + "http-terminator": "^3.2.0", + "pino": "^9.2.0", + "react": "^18.3.1", + "satori": "^0.10.13" + }, + "devDependencies": { + "@types/node": "^20.14.3", + "typescript": "^5.4.5" + } +} diff --git a/bskyogcard/src/assets/Inter-Bold.ttf b/bskyogcard/src/assets/Inter-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fe23eeb9c93a377d0f4ab003f1f77b555d19b1d1 GIT binary patch literal 316584 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 #OJ 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ȇ@(g~$ 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 literal 0 HcmV?d00001 diff --git a/bskyogcard/src/bin.ts b/bskyogcard/src/bin.ts new file mode 100644 index 0000000000..ff550809db --- /dev/null +++ b/bskyogcard/src/bin.ts @@ -0,0 +1,48 @@ +import cluster, {Worker} from 'node:cluster' + +import {envInt} from '@atproto/common' + +import {CardService, envToCfg, httpLogger, readEnv} from './index.js' + +async function main() { + const env = readEnv() + const cfg = envToCfg(env) + const card = await CardService.create(cfg) + await card.start() + httpLogger.info('card service is running') + process.on('SIGTERM', async () => { + httpLogger.info('card service is stopping') + await card.destroy() + httpLogger.info('card service is stopped') + if (cluster.isWorker) process.exit(0) + }) +} + +const workerCount = envInt('CARD_CLUSTER_WORKER_COUNT') + +if (workerCount) { + if (cluster.isPrimary) { + httpLogger.info(`primary ${process.pid} is running`) + const workers = new Set() + for (let i = 0; i < workerCount; ++i) { + workers.add(cluster.fork()) + } + let teardown = false + cluster.on('exit', worker => { + workers.delete(worker) + if (!teardown) { + workers.add(cluster.fork()) // restart on crash + } + }) + process.on('SIGTERM', () => { + teardown = true + httpLogger.info('disconnecting workers') + workers.forEach(w => w.kill('SIGTERM')) + }) + } else { + httpLogger.info(`worker ${process.pid} is running`) + main() + } +} else { + main() // non-clustering +} diff --git a/bskyogcard/src/components/Butterfly.tsx b/bskyogcard/src/components/Butterfly.tsx new file mode 100644 index 0000000000..5a4124975c --- /dev/null +++ b/bskyogcard/src/components/Butterfly.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +export function Butterfly(props: React.SVGAttributes) { + return ( + + + + ) +} diff --git a/bskyogcard/src/components/Img.tsx b/bskyogcard/src/components/Img.tsx new file mode 100644 index 0000000000..dac223180c --- /dev/null +++ b/bskyogcard/src/components/Img.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +export function Img( + props: Omit, 'src'> & {src: Buffer}, +) { + const {src, ...others} = props + return ( + + ) +} diff --git a/bskyogcard/src/components/StarterPack.tsx b/bskyogcard/src/components/StarterPack.tsx new file mode 100644 index 0000000000..f73442190c --- /dev/null +++ b/bskyogcard/src/components/StarterPack.tsx @@ -0,0 +1,149 @@ +/* eslint-disable bsky-internal/avoid-unwrapped-text */ +import React from 'react' +import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' + +import {Butterfly} from './Butterfly.js' +import {Img} from './Img.js' + +export const STARTERPACK_HEIGHT = 630 +export const STARTERPACK_WIDTH = 1200 +export const TILE_SIZE = STARTERPACK_HEIGHT / 3 + +const GRADIENT_TOP = '#0A7AFF' +const GRADIENT_BOTTOM = '#59B9FF' +const IMAGE_STROKE = '#359CFF' + +export function StarterPack(props: { + starterPack: AppBskyGraphDefs.StarterPackView + images: Map +}) { + const {starterPack, images} = props + const record = AppBskyGraphStarterpack.isRecord(starterPack.record) + ? starterPack.record + : null + const imagesArray = [...images.values()] + const imageOfCreator = images.get(starterPack.creator.did) + const imagesExceptCreator = [...images.entries()] + .filter(([did]) => did !== starterPack.creator.did) + .map(([, image]) => image) + const imagesAcross: Buffer[] = [] + if (imageOfCreator) { + if (imagesExceptCreator.length >= 6) { + imagesAcross.push(...imagesExceptCreator.slice(0, 3)) + imagesAcross.push(imageOfCreator) + imagesAcross.push(...imagesExceptCreator.slice(3, 6)) + } else { + const firstHalf = Math.floor(imagesExceptCreator.length / 2) + imagesAcross.push(...imagesExceptCreator.slice(0, firstHalf)) + imagesAcross.push(imageOfCreator) + imagesAcross.push( + ...imagesExceptCreator.slice(firstHalf, imagesExceptCreator.length), + ) + } + } else { + imagesAcross.push(...imagesExceptCreator.slice(0, 7)) + } + return ( +

+ {/* image tiles */} +
+ {[...Array(18)].map((_, i) => { + const image = imagesArray.at(i % imagesArray.length) + return ( +
+ {image && } +
+ ) + })} + {/* background overlay */} +
+
+ {/* foreground text & images */} +
+
+ JOIN THE CONVERSATION +
+
+ {imagesAcross.map((image, i) => { + return ( +
+ +
+ ) + })} +
+
+ {record?.name || 'Starter Pack'} +
+
+ on Bluesky +
+
+
+ ) +} diff --git a/bskyogcard/src/config.ts b/bskyogcard/src/config.ts new file mode 100644 index 0000000000..fafa18e743 --- /dev/null +++ b/bskyogcard/src/config.ts @@ -0,0 +1,40 @@ +import {envInt, envStr} from '@atproto/common' + +export type Config = { + service: ServiceConfig +} + +export type ServiceConfig = { + port: number + version?: string + appviewUrl: string + originVerify?: string +} + +export type Environment = { + port?: number + version?: string + appviewUrl?: string + originVerify?: string +} + +export const readEnv = (): Environment => { + return { + port: envInt('CARD_PORT'), + version: envStr('CARD_VERSION'), + appviewUrl: envStr('CARD_APPVIEW_URL'), + originVerify: envStr('CARD_ORIGIN_VERIFY'), + } +} + +export const envToCfg = (env: Environment): Config => { + const serviceCfg: ServiceConfig = { + port: env.port ?? 3000, + version: env.version, + appviewUrl: env.appviewUrl ?? 'https://api.bsky.app', + originVerify: env.originVerify, + } + return { + service: serviceCfg, + } +} diff --git a/bskyogcard/src/context.ts b/bskyogcard/src/context.ts new file mode 100644 index 0000000000..f92651cafb --- /dev/null +++ b/bskyogcard/src/context.ts @@ -0,0 +1,44 @@ +import {readFileSync} from 'node:fs' + +import {AtpAgent} from '@atproto/api' +import * as path from 'path' +import {fileURLToPath} from 'url' + +import {Config} from './config.js' + +const __DIRNAME = path.dirname(fileURLToPath(import.meta.url)) + +export type AppContextOptions = { + cfg: Config + appviewAgent: AtpAgent + fonts: {name: string; data: Buffer}[] +} + +export class AppContext { + cfg: Config + appviewAgent: AtpAgent + fonts: {name: string; data: Buffer}[] + abortController = new AbortController() + + constructor(private opts: AppContextOptions) { + this.cfg = this.opts.cfg + this.appviewAgent = this.opts.appviewAgent + this.fonts = this.opts.fonts + } + + static async fromConfig(cfg: Config, overrides?: Partial) { + const appviewAgent = new AtpAgent({service: cfg.service.appviewUrl}) + const fonts = [ + { + name: 'Inter', + data: readFileSync(path.join(__DIRNAME, 'assets', 'Inter-Bold.ttf')), + }, + ] + return new AppContext({ + cfg, + appviewAgent, + fonts, + ...overrides, + }) + } +} diff --git a/bskyogcard/src/index.ts b/bskyogcard/src/index.ts new file mode 100644 index 0000000000..ef8d48494d --- /dev/null +++ b/bskyogcard/src/index.ts @@ -0,0 +1,41 @@ +import events from 'node:events' +import http from 'node:http' + +import express from 'express' +import {createHttpTerminator, HttpTerminator} from 'http-terminator' + +import {Config} from './config.js' +import {AppContext} from './context.js' +import {default as routes, errorHandler} from './routes/index.js' + +export * from './config.js' +export * from './logger.js' + +export class CardService { + public server?: http.Server + private terminator?: HttpTerminator + + constructor(public app: express.Application, public ctx: AppContext) {} + + static async create(cfg: Config): Promise { + let app = express() + + const ctx = await AppContext.fromConfig(cfg) + app = routes(ctx, app) + app.use(errorHandler) + + return new CardService(app, ctx) + } + + async start() { + this.server = this.app.listen(this.ctx.cfg.service.port) + this.server.keepAliveTimeout = 90000 + this.terminator = createHttpTerminator({server: this.server}) + await events.once(this.server, 'listening') + } + + async destroy() { + this.ctx.abortController.abort() + await this.terminator?.terminate() + } +} diff --git a/bskyogcard/src/logger.ts b/bskyogcard/src/logger.ts new file mode 100644 index 0000000000..04b5d90469 --- /dev/null +++ b/bskyogcard/src/logger.ts @@ -0,0 +1,3 @@ +import {subsystemLogger} from '@atproto/common' + +export const httpLogger = subsystemLogger('bskyogcard') diff --git a/bskyogcard/src/routes/health.ts b/bskyogcard/src/routes/health.ts new file mode 100644 index 0000000000..0cc69515eb --- /dev/null +++ b/bskyogcard/src/routes/health.ts @@ -0,0 +1,14 @@ +import {Express} from 'express' + +import {AppContext} from '../context.js' +import {handler} from './util.js' + +export default function (ctx: AppContext, app: Express) { + return app.get( + '/_health', + handler(async (_req, res) => { + const {version} = ctx.cfg.service + return res.send({version}) + }), + ) +} diff --git a/bskyogcard/src/routes/index.ts b/bskyogcard/src/routes/index.ts new file mode 100644 index 0000000000..0c40f89d3b --- /dev/null +++ b/bskyogcard/src/routes/index.ts @@ -0,0 +1,13 @@ +import {Express} from 'express' + +import {AppContext} from '../context.js' +import {default as health} from './health.js' +import {default as starterPack} from './starter-pack.js' + +export * from './util.js' + +export default function (ctx: AppContext, app: Express) { + app = health(ctx, app) // GET /_health + app = starterPack(ctx, app) // GET /start/:actor/:rkey + return app +} diff --git a/bskyogcard/src/routes/starter-pack.tsx b/bskyogcard/src/routes/starter-pack.tsx new file mode 100644 index 0000000000..cb3a553272 --- /dev/null +++ b/bskyogcard/src/routes/starter-pack.tsx @@ -0,0 +1,102 @@ +import assert from 'node:assert' + +import React from 'react' +import {AppBskyGraphDefs, AtUri} from '@atproto/api' +import resvg from '@resvg/resvg-js' +import {Express} from 'express' +import satori from 'satori' + +import { + StarterPack, + STARTERPACK_HEIGHT, + STARTERPACK_WIDTH, +} from '../components/StarterPack.js' +import {AppContext} from '../context.js' +import {httpLogger} from '../logger.js' +import {handler, originVerifyMiddleware} from './util.js' + +export default function (ctx: AppContext, app: Express) { + return app.get( + '/start/:actor/:rkey', + originVerifyMiddleware(ctx), + handler(async (req, res) => { + const {actor, rkey} = req.params + const uri = AtUri.make(actor, 'app.bsky.graph.starterpack', rkey) + let starterPack: AppBskyGraphDefs.StarterPackView + try { + const result = await ctx.appviewAgent.api.app.bsky.graph.getStarterPack( + {starterPack: uri.toString()}, + ) + starterPack = result.data.starterPack + } catch (err) { + httpLogger.warn( + {err, uri: uri.toString()}, + 'could not fetch starter pack', + ) + return res.status(404).end('not found') + } + const imageEntries = await Promise.all( + [starterPack.creator] + .concat(starterPack.listItemsSample.map(li => li.subject)) + // has avatar + .filter(p => p.avatar) + // no sensitive labels + .filter(p => !p.labels.some(l => hideAvatarLabels.has(l.val))) + .map(async p => { + try { + assert(p.avatar) + const image = await getImage(p.avatar) + return [p.did, image] as const + } catch (err) { + httpLogger.warn( + {err, uri: uri.toString(), did: p.did}, + 'could not fetch image', + ) + return [p.did, null] as const + } + }), + ) + const images = new Map( + imageEntries.filter(([_, image]) => image !== null).slice(0, 7), + ) + const svg = await satori( + , + { + fonts: ctx.fonts, + height: STARTERPACK_HEIGHT, + width: STARTERPACK_WIDTH, + }, + ) + const output = await resvg.renderAsync(svg) + res.statusCode = 200 + res.setHeader('content-type', 'image/png') + res.setHeader('cdn-tag', [...images.keys()].join(',')) + return res.end(output.asPng()) + }), + ) +} + +async function getImage(url: string) { + const response = await fetch(url) + const arrayBuf = await response.arrayBuffer() // must drain body even if it will be discarded + if (response.status !== 200) return null + return Buffer.from(arrayBuf) +} + +const hideAvatarLabels = new Set([ + '!hide', + '!warn', + 'porn', + 'sexual', + 'nudity', + 'sexual-figurative', + 'graphic-media', + 'self-harm', + 'sensitive', + 'security', + 'impersonation', + 'scam', + 'spam', + 'misleading', + 'inauthentic', +]) diff --git a/bskyogcard/src/routes/util.ts b/bskyogcard/src/routes/util.ts new file mode 100644 index 0000000000..718ed592a1 --- /dev/null +++ b/bskyogcard/src/routes/util.ts @@ -0,0 +1,36 @@ +import {ErrorRequestHandler, Request, RequestHandler, Response} from 'express' + +import {AppContext} from '../context.js' +import {httpLogger} from '../logger.js' + +export type Handler = (req: Request, res: Response) => Awaited + +export const handler = (runHandler: Handler): RequestHandler => { + return async (req, res, next) => { + try { + await runHandler(req, res) + } catch (err) { + next(err) + } + } +} + +export function originVerifyMiddleware(ctx: AppContext): RequestHandler { + const {originVerify} = ctx.cfg.service + if (!originVerify) return (_req, _res, next) => next() + return (req, res, next) => { + const verifyHeader = req.headers['x-origin-verify'] + if (verifyHeader !== originVerify) { + return res.status(404).end('not found') + } + next() + } +} + +export const errorHandler: ErrorRequestHandler = (err, req, res, next) => { + httpLogger.error({err}, 'request error') + if (res.headersSent) { + return next(err) + } + return res.status(500).end('server error') +} diff --git a/bskyogcard/tsconfig.json b/bskyogcard/tsconfig.json new file mode 100644 index 0000000000..a5c3beecb1 --- /dev/null +++ b/bskyogcard/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "esModuleInterop": true, + "moduleResolution": "NodeNext", + "jsx": "react-jsx", + "outDir": "dist" + }, + "include": ["./src/index.ts", "./src/bin.ts"] +} diff --git a/bskyogcard/yarn.lock b/bskyogcard/yarn.lock new file mode 100644 index 0000000000..0403efb84e --- /dev/null +++ b/bskyogcard/yarn.lock @@ -0,0 +1,1113 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@atproto/api@0.12.19-next.0": + version "0.12.19-next.0" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.19-next.0.tgz#9592476cbdba8482d0fd8d65e20275c95d6d5fd4" + integrity sha512-wyWr4uIabTgDTBY99y3QyrFxcIx1Mh4DkURgSv8sd/b+w0lfrZAJh0Gg9BXdg/iIjcf/M2lCTL04r0vASfkMVg== + dependencies: + "@atproto/common-web" "^0.3.0" + "@atproto/lexicon" "^0.4.0" + "@atproto/syntax" "^0.3.0" + "@atproto/xrpc" "^0.5.0" + multiformats "^9.9.0" + tlds "^1.234.0" + +"@atproto/common-web@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.0.tgz#36da8c2c31d8cf8a140c3c8f03223319bf4430bb" + integrity sha512-67VnV6JJyX+ZWyjV7xFQMypAgDmjVaR9ZCuU/QW+mqlqI7fex2uL4Fv+7/jHadgzhuJHVd6OHOvNn0wR5WZYtA== + dependencies: + graphemer "^1.4.0" + multiformats "^9.9.0" + uint8arrays "3.0.0" + zod "^3.21.4" + +"@atproto/common@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.0.tgz#d77696c7eb545426df727837d9ee333b429fe7ef" + integrity sha512-yOXuPlCjT/OK9j+neIGYn9wkxx/AlxQSucysAF0xgwu0Ji8jAtKBf9Jv6R5ObYAjAD/kVUvEYumle+Yq/R9/7g== + dependencies: + "@atproto/common-web" "^0.3.0" + "@ipld/dag-cbor" "^7.0.3" + cbor-x "^1.5.1" + iso-datestring-validator "^2.2.2" + multiformats "^9.9.0" + pino "^8.15.0" + +"@atproto/lexicon@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.0.tgz#63e8829945d80c25524882caa8ed27b1151cc576" + integrity sha512-RvCBKdSI4M8qWm5uTNz1z3R2yIvIhmOsMuleOj8YR6BwRD+QbtUBy3l+xQ7iXf4M5fdfJFxaUNa6Ty0iRwdKqQ== + dependencies: + "@atproto/common-web" "^0.3.0" + "@atproto/syntax" "^0.3.0" + iso-datestring-validator "^2.2.2" + multiformats "^9.9.0" + zod "^3.21.4" + +"@atproto/syntax@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.0.tgz#fafa2dbea9add37253005cb663e7373e05e618b3" + integrity sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA== + +"@atproto/xrpc@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.5.0.tgz#dacbfd8f7b13f0ab5bd56f8fdd4b460e132a6032" + integrity sha512-swu+wyOLvYW4l3n+VAuJbHcPcES+tin2Lsrp8Bw5aIXIICiuFn1YMFlwK9JwVUzTH21Py1s1nHEjr4CJeElJog== + dependencies: + "@atproto/lexicon" "^0.4.0" + zod "^3.21.4" + +"@cbor-extract/cbor-extract-darwin-arm64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz#8d65cb861a99622e1b4a268e2d522d2ec6137338" + integrity sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w== + +"@cbor-extract/cbor-extract-darwin-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz#9fbec199c888c5ec485a1839f4fad0485ab6c40a" + integrity sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w== + +"@cbor-extract/cbor-extract-linux-arm64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz#bf77e0db4a1d2200a5aa072e02210d5043e953ae" + integrity sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ== + +"@cbor-extract/cbor-extract-linux-arm@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz#491335037eb8533ed8e21b139c59f6df04e39709" + integrity sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q== + +"@cbor-extract/cbor-extract-linux-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz#672574485ccd24759bf8fb8eab9dbca517d35b97" + integrity sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw== + +"@cbor-extract/cbor-extract-win32-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz#4b3f07af047f984c082de34b116e765cb9af975f" + integrity sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w== + +"@ipld/dag-cbor@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz#aa31b28afb11a807c3d627828a344e5521ac4a1e" + integrity sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA== + dependencies: + cborg "^1.6.0" + multiformats "^9.5.4" + +"@resvg/resvg-js-android-arm-eabi@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz#e761e0b688127db64879f455178c92468a9aeabe" + integrity sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA== + +"@resvg/resvg-js-android-arm64@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz#b8cb564d7f6b3f37d9b43129f5dc5fe171e249e4" + integrity sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ== + +"@resvg/resvg-js-darwin-arm64@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz#49bd3faeda5c49f53302d970e6e79d006de18e7d" + integrity sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A== + +"@resvg/resvg-js-darwin-x64@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz#e1344173aa27bfb4d880ab576d1acf1c1648faca" + integrity sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw== + +"@resvg/resvg-js-linux-arm-gnueabihf@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz#34c445eba45efd68f6130b2ab426d76a7424253d" + integrity sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw== + +"@resvg/resvg-js-linux-arm64-gnu@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz#30da47087dd8153182198b94fe9f8d994890dae5" + integrity sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg== + +"@resvg/resvg-js-linux-arm64-musl@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz#5d75b8ff5c83103729c1ca3779987302753c50d4" + integrity sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg== + +"@resvg/resvg-js-linux-x64-gnu@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz#411abedfaee5edc57cbb7701736cecba522e26f3" + integrity sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw== + +"@resvg/resvg-js-linux-x64-musl@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz#fe4984038f0372f279e3ff570b72934dd7eb2a5c" + integrity sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ== + +"@resvg/resvg-js-win32-arm64-msvc@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz#d3a053cf7ff687087a2106330c0fdaae706254d1" + integrity sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ== + +"@resvg/resvg-js-win32-ia32-msvc@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz#7cdda1ce29ef7209e28191d917fa5bef0624a4ad" + integrity sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w== + +"@resvg/resvg-js-win32-x64-msvc@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz#cb0ad04525d65f3def4c8d346157a57976d5b388" + integrity sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ== + +"@resvg/resvg-js@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@resvg/resvg-js/-/resvg-js-2.6.2.tgz#3e92a907d88d879256c585347c5b21a7f3bb5b46" + integrity sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q== + optionalDependencies: + "@resvg/resvg-js-android-arm-eabi" "2.6.2" + "@resvg/resvg-js-android-arm64" "2.6.2" + "@resvg/resvg-js-darwin-arm64" "2.6.2" + "@resvg/resvg-js-darwin-x64" "2.6.2" + "@resvg/resvg-js-linux-arm-gnueabihf" "2.6.2" + "@resvg/resvg-js-linux-arm64-gnu" "2.6.2" + "@resvg/resvg-js-linux-arm64-musl" "2.6.2" + "@resvg/resvg-js-linux-x64-gnu" "2.6.2" + "@resvg/resvg-js-linux-x64-musl" "2.6.2" + "@resvg/resvg-js-win32-arm64-msvc" "2.6.2" + "@resvg/resvg-js-win32-ia32-msvc" "2.6.2" + "@resvg/resvg-js-win32-x64-msvc" "2.6.2" + +"@shuding/opentype.js@1.4.0-beta.0": + version "1.4.0-beta.0" + resolved "https://registry.yarnpkg.com/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz#5d1e7e9e056f546aad41df1c5043f8f85d39e24b" + integrity sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA== + dependencies: + fflate "^0.7.3" + string.prototype.codepointat "^0.2.1" + +"@types/node@^20.14.3": + version "20.14.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.3.tgz#7a9a5d009b0861e7f337166dc435dbfd758db92d" + integrity sha512-Nuzqa6WAxeGnve6SXqiPAM9rA++VQs+iLZ1DDd56y0gdvygSZlQvZuvdFPR3yLqkVxPu4WrO02iDEyH1g+wazw== + dependencies: + undici-types "~5.26.4" + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + +base64-js@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" + integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +boolean@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" + integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +camelize@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" + integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== + +cbor-extract@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cbor-extract/-/cbor-extract-2.2.0.tgz#cee78e630cbeae3918d1e2e58e0cebaf3a3be840" + integrity sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA== + dependencies: + node-gyp-build-optional-packages "5.1.1" + optionalDependencies: + "@cbor-extract/cbor-extract-darwin-arm64" "2.2.0" + "@cbor-extract/cbor-extract-darwin-x64" "2.2.0" + "@cbor-extract/cbor-extract-linux-arm" "2.2.0" + "@cbor-extract/cbor-extract-linux-arm64" "2.2.0" + "@cbor-extract/cbor-extract-linux-x64" "2.2.0" + "@cbor-extract/cbor-extract-win32-x64" "2.2.0" + +cbor-x@^1.5.1: + version "1.5.9" + resolved "https://registry.yarnpkg.com/cbor-x/-/cbor-x-1.5.9.tgz#ed6b2afcd7884bdd697674bfb7332c1473a13ecf" + integrity sha512-OEI5rEu3MeR0WWNUXuIGkxmbXVhABP+VtgAXzm48c9ulkrsvxshjjk94XSOGphyAKeNGLPfAxxzEtgQ6rEVpYQ== + optionalDependencies: + cbor-extract "^2.2.0" + +cborg@^1.6.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/cborg/-/cborg-1.10.2.tgz#83cd581b55b3574c816f82696307c7512db759a1" + integrity sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug== + +color-name@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +css-background-parser@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/css-background-parser/-/css-background-parser-0.1.0.tgz#48a17f7fe6d4d4f1bca3177ddf16c5617950741b" + integrity sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA== + +css-box-shadow@1.0.0-3: + version "1.0.0-3" + resolved "https://registry.yarnpkg.com/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz#9eaeb7140947bf5d649fc49a19e4bbaa5f602713" + integrity sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg== + +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== + +css-to-react-native@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" + integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +delay@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" + integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-libc@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +emoji-regex@^10.2.1: + version "10.3.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" + integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +escape-html@^1.0.3, escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +express@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +fast-printf@^1.6.9: + version "1.6.9" + resolved "https://registry.yarnpkg.com/fast-printf/-/fast-printf-1.6.9.tgz#212f56570d2dc8ccdd057ee93d50dd414d07d676" + integrity sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg== + dependencies: + boolean "^3.1.4" + +fast-redact@^3.1.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.5.0.tgz#e9ea02f7e57d0cd8438180083e93077e496285e4" + integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== + +fflate@^0.7.3: + version "0.7.4" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50" + integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw== + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hex-rgb@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/hex-rgb/-/hex-rgb-4.3.0.tgz#af5e974e83bb2fefe44d55182b004ec818c07776" + integrity sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-terminator@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/http-terminator/-/http-terminator-3.2.0.tgz#bc158d2694b733ca4fbf22a35065a81a609fb3e9" + integrity sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g== + dependencies: + delay "^5.0.0" + p-wait-for "^3.2.0" + roarr "^7.0.4" + type-fest "^2.3.3" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +iso-datestring-validator@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz#2daa80d2900b7a954f9f731d42f96ee0c19a6895" + integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +linebreak@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/linebreak/-/linebreak-1.1.0.tgz#831cf378d98bced381d8ab118f852bd50d81e46b" + integrity sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ== + dependencies: + base64-js "0.0.8" + unicode-trie "^2.0.0" + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multiformats@^9.4.2, multiformats@^9.5.4, multiformats@^9.9.0: + version "9.9.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +node-gyp-build-optional-packages@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz#52b143b9dd77b7669073cbfe39e3f4118bfc603c" + integrity sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw== + dependencies: + detect-libc "^2.0.1" + +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +on-exit-leak-free@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" + integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-timeout@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +p-wait-for@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-wait-for/-/p-wait-for-3.2.0.tgz#640429bcabf3b0dd9f492c31539c5718cb6a3f1f" + integrity sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA== + dependencies: + p-timeout "^3.0.0" + +pako@^0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== + +parse-css-color@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/parse-css-color/-/parse-css-color-0.2.1.tgz#b687a583f2e42e66ffdfce80a570706966e807c9" + integrity sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg== + dependencies: + color-name "^1.1.4" + hex-rgb "^4.1.0" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +pino-abstract-transport@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz#97f9f2631931e242da531b5c66d3079c12c9d1b5" + integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q== + dependencies: + readable-stream "^4.0.0" + split2 "^4.0.0" + +pino-std-serializers@^6.0.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz#d9a9b5f2b9a402486a5fc4db0a737570a860aab3" + integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== + +pino-std-serializers@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b" + integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== + +pino@^8.15.0: + version "8.21.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.21.0.tgz#e1207f3675a2722940d62da79a7a55a98409f00d" + integrity sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^1.2.0" + pino-std-serializers "^6.0.0" + process-warning "^3.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^3.7.0" + thread-stream "^2.6.0" + +pino@^9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-9.2.0.tgz#e77a9516f3a3e5550d9b76d9f65ac6118ef02bdd" + integrity sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^1.2.0" + pino-std-serializers "^7.0.0" + process-warning "^3.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^4.0.1" + thread-stream "^3.0.0" + +postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +process-warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-3.0.0.tgz#96e5b88884187a1dce6f5c3166d611132058710b" + integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +react@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== + dependencies: + loose-envify "^1.1.0" + +readable-stream@^4.0.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" + integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + +roarr@^7.0.4: + version "7.21.1" + resolved "https://registry.yarnpkg.com/roarr/-/roarr-7.21.1.tgz#fd6452ca822a65f736c35e5372f04ee9f2ca3851" + integrity sha512-3niqt5bXFY1InKU8HKWqqYTYjtrBaxBMnXELXCXUYgtNYGUtZM5rB46HIC430AyacL95iEniGf7RgqsesykLmQ== + dependencies: + fast-printf "^1.6.9" + safe-stable-stringify "^2.4.3" + semver-compare "^1.0.0" + +safe-buffer@5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-stable-stringify@^2.3.1, safe-stable-stringify@^2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +satori@^0.10.13: + version "0.10.13" + resolved "https://registry.yarnpkg.com/satori/-/satori-0.10.13.tgz#658a9920f55268d2002819387a80a0b6d4bdc262" + integrity sha512-klCwkVYMQ/ZN5inJLHzrUmGwoRfsdP7idB5hfpJ1jfiJk1ErDitK8Hkc6Kll1+Ox2WtqEuGecSZLnmup3CGzvQ== + dependencies: + "@shuding/opentype.js" "1.4.0-beta.0" + css-background-parser "^0.1.0" + css-box-shadow "1.0.0-3" + css-to-react-native "^3.0.0" + emoji-regex "^10.2.1" + escape-html "^1.0.3" + linebreak "^1.1.0" + parse-css-color "^0.2.1" + postcss-value-parser "^4.2.0" + yoga-wasm-web "^0.3.3" + +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +sonic-boom@^3.7.0: + version "3.8.1" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.8.1.tgz#d5ba8c4e26d6176c9a1d14d549d9ff579a163422" + integrity sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg== + dependencies: + atomic-sleep "^1.0.0" + +sonic-boom@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.0.1.tgz#515b7cef2c9290cb362c4536388ddeece07aed30" + integrity sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ== + dependencies: + atomic-sleep "^1.0.0" + +split2@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +string.prototype.codepointat@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc" + integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg== + +string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +thread-stream@^2.6.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.7.0.tgz#d8a8e1b3fd538a6cca8ce69dbe5d3d097b601e11" + integrity sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw== + dependencies: + real-require "^0.2.0" + +thread-stream@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-3.1.0.tgz#4b2ef252a7c215064507d4ef70c05a5e2d34c4f1" + integrity sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A== + dependencies: + real-require "^0.2.0" + +tiny-inflate@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + +tlds@^1.234.0: + version "1.252.0" + resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.252.0.tgz#71d9617f4ef4cc7347843bee72428e71b8b0f419" + integrity sha512-GA16+8HXvqtfEnw/DTcwB0UU354QE1n3+wh08oFjr6Znl7ZLAeUgYzCcK+/CCrOyE0vnHR8/pu3XXG3vDijXpQ== + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +type-fest@^2.3.3: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@^5.4.5: + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== + +uint8arrays@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.0.0.tgz#260869efb8422418b6f04e3fac73a3908175c63b" + integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== + dependencies: + multiformats "^9.4.2" + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +unicode-trie@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-2.0.0.tgz#8fd8845696e2e14a8b67d78fa9e0dd2cad62fec8" + integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ== + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +yoga-wasm-web@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz#eb8e9fcb18e5e651994732f19a220cb885d932ba" + integrity sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA== + +zod@^3.21.4: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== From 5d98b4b06c272d1a3a0b91f4f509c6e733c89d4f Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 21 Jun 2024 00:54:30 +0300 Subject: [PATCH 077/127] Wait for AppView when posting (#4584) --- src/view/com/composer/Composer.tsx | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 80bce5351c..9e2f77d4df 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -24,12 +24,18 @@ import Animated, { } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {LinearGradient} from 'expo-linear-gradient' +import { + AppBskyFeedDefs, + AppBskyFeedGetPostThread, + BskyAgent, +} from '@atproto/api' import {RichText} from '@atproto/api' 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 {until} from '#/lib/async/until' import { createGIFDescription, parseAltFromGIFDescription, @@ -299,6 +305,17 @@ export const ComposePost = observer(function ComposePost({ langs: toPostLanguages(langPrefs.postLanguage), }) ).uri + try { + await whenAppViewReady(agent, postUri, res => { + const thread = res.data.thread + return AppBskyFeedDefs.isThreadViewPost(thread) + }) + } catch (waitErr: any) { + logger.error(waitErr, { + message: `Waiting for app view failed`, + }) + // Keep going because the post *was* published. + } } catch (e: any) { logger.error(e, { message: `Composer: create post failed`, @@ -756,6 +773,23 @@ function useKeyboardVerticalOffset() { return top + 10 } +async function whenAppViewReady( + agent: BskyAgent, + uri: string, + fn: (res: AppBskyFeedGetPostThread.Response) => boolean, +) { + await until( + 5, // 5 tries + 1e3, // 1s delay between tries + fn, + () => + agent.app.bsky.feed.getPostThread({ + uri, + depth: 0, + }), + ) +} + const styles = StyleSheet.create({ topbarInner: { flexDirection: 'row', From 4d8537bcd46866c9c613cdb8a1ff9ae77ed52dfa Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 20 Jun 2024 15:01:58 -0700 Subject: [PATCH 078/127] center pill text in label pill (#4579) * center pill text * undo --- src/view/com/profile/ProfileCard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 2b0790002d..a3cd5ca1b9 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -328,6 +328,7 @@ const styles = StyleSheet.create({ borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2, + justifyContent: 'center', }, btn: { paddingVertical: 7, From 4bba59790a04d9c708dd3cbecf96fdab7f306d94 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 20 Jun 2024 17:06:57 -0500 Subject: [PATCH 079/127] Add a11y context (#4586) * Add a11y context * Feedback --- src/App.native.tsx | 45 +++++++++++++++++--------------- src/App.web.tsx | 41 +++++++++++++++-------------- src/state/a11y.tsx | 65 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 40 deletions(-) create mode 100644 src/state/a11y.tsx diff --git a/src/App.native.tsx b/src/App.native.tsx index 18461fdd05..4c73d87525 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -24,6 +24,7 @@ import { import {s} from '#/lib/styles' import {ThemeProvider} from '#/lib/ThemeContext' import {logger} from '#/logger' +import {Provider as A11yProvider} from '#/state/a11y' import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' import {Provider as DialogStateProvider} from '#/state/dialogs' import {Provider as InvitesStateProvider} from '#/state/invites' @@ -152,27 +153,29 @@ function App() { * that is set up in the InnerApp component above. */ return ( - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + ) } diff --git a/src/App.web.tsx b/src/App.web.tsx index 6af3c7d6fb..00939c9eb4 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -13,6 +13,7 @@ import {QueryProvider} from '#/lib/react-query' import {Provider as StatsigProvider} from '#/lib/statsig/statsig' import {ThemeProvider} from '#/lib/ThemeContext' import {logger} from '#/logger' +import {Provider as A11yProvider} from '#/state/a11y' import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' import {Provider as DialogStateProvider} from '#/state/dialogs' import {Provider as InvitesStateProvider} from '#/state/invites' @@ -135,25 +136,27 @@ function App() { * that is set up in the InnerApp component above. */ return ( - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + ) } diff --git a/src/state/a11y.tsx b/src/state/a11y.tsx new file mode 100644 index 0000000000..aefcfd1ec4 --- /dev/null +++ b/src/state/a11y.tsx @@ -0,0 +1,65 @@ +import React from 'react' +import {AccessibilityInfo} from 'react-native' +import {isReducedMotion} from 'react-native-reanimated' + +import {isWeb} from '#/platform/detection' + +const Context = React.createContext({ + reduceMotionEnabled: false, + screenReaderEnabled: false, +}) + +export function useA11y() { + return React.useContext(Context) +} + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [reduceMotionEnabled, setReduceMotionEnabled] = React.useState(() => + isReducedMotion(), + ) + const [screenReaderEnabled, setScreenReaderEnabled] = React.useState(false) + + React.useEffect(() => { + const reduceMotionChangedSubscription = AccessibilityInfo.addEventListener( + 'reduceMotionChanged', + enabled => { + setReduceMotionEnabled(enabled) + }, + ) + const screenReaderChangedSubscription = AccessibilityInfo.addEventListener( + 'screenReaderChanged', + enabled => { + setScreenReaderEnabled(enabled) + }, + ) + + ;(async () => { + const [_reduceMotionEnabled, _screenReaderEnabled] = await Promise.all([ + AccessibilityInfo.isReduceMotionEnabled(), + AccessibilityInfo.isScreenReaderEnabled(), + ]) + setReduceMotionEnabled(_reduceMotionEnabled) + setScreenReaderEnabled(_screenReaderEnabled) + })() + + return () => { + reduceMotionChangedSubscription.remove() + screenReaderChangedSubscription.remove() + } + }, []) + + const ctx = React.useMemo(() => { + return { + reduceMotionEnabled, + /** + * Always returns true on web. For now, we're using this for mobile a11y, + * so we reset to false on web. + * + * @see https://github.com/necolas/react-native-web/discussions/2072 + */ + screenReaderEnabled: isWeb ? false : screenReaderEnabled, + } + }, [reduceMotionEnabled, screenReaderEnabled]) + + return {children} +} From 4c48a1f14b317a76e55c008aeeb834ebb8f416d0 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 21 Jun 2024 01:47:56 +0300 Subject: [PATCH 080/127] [Session] Logging (#4476) * Add session logging (console.log) * Hook it up for real * Send type separately --- src/state/session/index.tsx | 42 +++++++++-- src/state/session/logging.ts | 137 +++++++++++++++++++++++++++++++++++ src/state/session/reducer.ts | 5 +- 3 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 src/state/session/logging.ts diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index 314945bcf9..3aac19025d 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -19,6 +19,7 @@ import { import {getInitialState, reducer} from './reducer' export {isSignupQueued} from './util' +import {addSessionDebugLog} from './logging' export type {SessionAccount} from '#/state/session/types' import {SessionApiContext, SessionStateContext} from '#/state/session/types' @@ -40,9 +41,11 @@ const ApiContext = React.createContext({ export function Provider({children}: React.PropsWithChildren<{}>) { const cancelPendingTask = useOneTaskAtATime() - const [state, dispatch] = React.useReducer(reducer, null, () => - getInitialState(persisted.get('session').accounts), - ) + const [state, dispatch] = React.useReducer(reducer, null, () => { + const initialState = getInitialState(persisted.get('session').accounts) + addSessionDebugLog({type: 'reducer:init', state: initialState}) + return initialState + }) const onAgentSessionChange = React.useCallback( (agent: BskyAgent, accountDid: string, sessionEvent: AtpSessionEvent) => { @@ -63,6 +66,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const createAccount = React.useCallback( async params => { + addSessionDebugLog({type: 'method:start', method: 'createAccount'}) const signal = cancelPendingTask() track('Try Create Account') logEvent('account:create:begin', {}) @@ -81,12 +85,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) { }) track('Create Account') logEvent('account:create:success', {}) + addSessionDebugLog({type: 'method:end', method: 'createAccount', account}) }, [onAgentSessionChange, cancelPendingTask], ) const login = React.useCallback( async (params, logContext) => { + addSessionDebugLog({type: 'method:start', method: 'login'}) const signal = cancelPendingTask() const {agent, account} = await createAgentAndLogin( params, @@ -103,23 +109,31 @@ export function Provider({children}: React.PropsWithChildren<{}>) { }) track('Sign In', {resumedSession: false}) logEvent('account:loggedIn', {logContext, withPassword: true}) + addSessionDebugLog({type: 'method:end', method: 'login', account}) }, [onAgentSessionChange, cancelPendingTask], ) const logout = React.useCallback( logContext => { + addSessionDebugLog({type: 'method:start', method: 'logout'}) cancelPendingTask() dispatch({ type: 'logged-out', }) logEvent('account:loggedOut', {logContext}) + addSessionDebugLog({type: 'method:end', method: 'logout'}) }, [cancelPendingTask], ) const resumeSession = React.useCallback( async storedAccount => { + addSessionDebugLog({ + type: 'method:start', + method: 'resumeSession', + account: storedAccount, + }) const signal = cancelPendingTask() const {agent, account} = await createAgentAndResume( storedAccount, @@ -134,17 +148,24 @@ export function Provider({children}: React.PropsWithChildren<{}>) { newAgent: agent, newAccount: account, }) + addSessionDebugLog({type: 'method:end', method: 'resumeSession', account}) }, [onAgentSessionChange, cancelPendingTask], ) const removeAccount = React.useCallback( account => { + addSessionDebugLog({ + type: 'method:start', + method: 'removeAccount', + account, + }) cancelPendingTask() dispatch({ type: 'removed-account', accountDid: account.did, }) + addSessionDebugLog({type: 'method:end', method: 'removeAccount', account}) }, [cancelPendingTask], ) @@ -152,18 +173,21 @@ export function Provider({children}: React.PropsWithChildren<{}>) { React.useEffect(() => { if (state.needsPersist) { state.needsPersist = false - persisted.write('session', { + const persistedData = { accounts: state.accounts, currentAccount: state.accounts.find( a => a.did === state.currentAgentState.did, ), - }) + } + addSessionDebugLog({type: 'persisted:broadcast', data: persistedData}) + persisted.write('session', persistedData) } }, [state]) React.useEffect(() => { return persisted.onUpdate(() => { const synced = persisted.get('session') + addSessionDebugLog({type: 'persisted:receive', data: synced}) dispatch({ type: 'synced-accounts', syncedAccounts: synced.accounts, @@ -177,7 +201,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) { resumeSession(syncedAccount) } else { const agent = state.currentAgentState.agent as BskyAgent + const prevSession = agent.session agent.session = sessionAccountToSession(syncedAccount) + addSessionDebugLog({ + type: 'agent:patch', + agent, + prevSession, + nextSession: agent.session, + }) } } }) @@ -215,6 +246,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { // Read the previous value and immediately advance the pointer. const prevAgent = currentAgentRef.current currentAgentRef.current = agent + addSessionDebugLog({type: 'agent:switch', prevAgent, nextAgent: agent}) // We never reuse agents so let's fully neutralize the previous one. // This ensures it won't try to consume any refresh tokens. prevAgent.session = undefined diff --git a/src/state/session/logging.ts b/src/state/session/logging.ts new file mode 100644 index 0000000000..16aa66fe72 --- /dev/null +++ b/src/state/session/logging.ts @@ -0,0 +1,137 @@ +import {AtpSessionData} from '@atproto/api' +import {sha256} from 'js-sha256' +import {Statsig} from 'statsig-react-native-expo' + +import {Schema} from '../persisted' +import {Action, State} from './reducer' +import {SessionAccount} from './types' + +type Reducer = (state: State, action: Action) => State + +type Log = + | { + type: 'reducer:init' + state: State + } + | { + type: 'reducer:call' + action: Action + prevState: State + nextState: State + } + | { + type: 'method:start' + method: + | 'createAccount' + | 'login' + | 'logout' + | 'resumeSession' + | 'removeAccount' + account?: SessionAccount + } + | { + type: 'method:end' + method: + | 'createAccount' + | 'login' + | 'logout' + | 'resumeSession' + | 'removeAccount' + account?: SessionAccount + } + | { + type: 'persisted:broadcast' + data: Schema['session'] + } + | { + type: 'persisted:receive' + data: Schema['session'] + } + | { + type: 'agent:switch' + prevAgent: object + nextAgent: object + } + | { + type: 'agent:patch' + agent: object + prevSession: AtpSessionData | undefined + nextSession: AtpSessionData + } + +export function wrapSessionReducerForLogging(reducer: Reducer): Reducer { + return function loggingWrapper(prevState: State, action: Action): State { + const nextState = reducer(prevState, action) + addSessionDebugLog({type: 'reducer:call', prevState, action, nextState}) + return nextState + } +} + +let nextMessageIndex = 0 +const MAX_SLICE_LENGTH = 1000 + +export function addSessionDebugLog(log: Log) { + try { + if (!Statsig.initializeCalled() || !Statsig.getStableID()) { + // Drop these logs for now. + return + } + if (!Statsig.checkGate('debug_session')) { + return + } + const messageIndex = nextMessageIndex++ + const {type, ...content} = log + let payload = JSON.stringify(content, replacer) + + let nextSliceIndex = 0 + while (payload.length > 0) { + const sliceIndex = nextSliceIndex++ + const slice = payload.slice(0, MAX_SLICE_LENGTH) + payload = payload.slice(MAX_SLICE_LENGTH) + Statsig.logEvent('session:debug', null, { + realmId, + messageIndex: String(messageIndex), + messageType: type, + sliceIndex: String(sliceIndex), + slice, + }) + } + } catch (e) { + console.error(e) + } +} + +let agentIds = new WeakMap() +let realmId = Math.random().toString(36).slice(2) +let nextAgentId = 1 + +function getAgentId(agent: object) { + let id = agentIds.get(agent) + if (id === undefined) { + id = realmId + '::' + nextAgentId++ + agentIds.set(agent, id) + } + return id +} + +function replacer(key: string, value: unknown) { + if (typeof value === 'object' && value != null && 'api' in value) { + return getAgentId(value) + } + if ( + key === 'service' || + key === 'email' || + key === 'emailConfirmed' || + key === 'emailAuthFactor' || + key === 'pdsUrl' + ) { + return undefined + } + if ( + typeof value === 'string' && + (key === 'refreshJwt' || key === 'accessJwt') + ) { + return sha256(value) + } + return value +} diff --git a/src/state/session/reducer.ts b/src/state/session/reducer.ts index 7f30809353..0a537b42c6 100644 --- a/src/state/session/reducer.ts +++ b/src/state/session/reducer.ts @@ -1,6 +1,7 @@ import {AtpSessionEvent} from '@atproto/api' import {createPublicAgent} from './agent' +import {wrapSessionReducerForLogging} from './logging' import {SessionAccount} from './types' // A hack so that the reducer can't read anything from the agent. @@ -64,7 +65,7 @@ export function getInitialState(persistedAccounts: SessionAccount[]): State { } } -export function reducer(state: State, action: Action): State { +let reducer = (state: State, action: Action): State => { switch (action.type) { case 'received-agent-event': { const {agent, accountDid, refreshedAccount, sessionEvent} = action @@ -166,3 +167,5 @@ export function reducer(state: State, action: Action): State { } } } +reducer = wrapSessionReducerForLogging(reducer) +export {reducer} From f344032ed8fde2ecbfc1a71f12373eb013fb4fd7 Mon Sep 17 00:00:00 2001 From: Marco Maroni <166719395+marcomaroni-github@users.noreply.github.com> Date: Fri, 21 Jun 2024 00:48:58 +0200 Subject: [PATCH 081/127] Italian translation fix (#4527) * Update messages.po Italian translation fix (feeds->feed) * Italian localization update Italian localization update (DM) * Update src/locale/locales/it/messages.po Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/locale/locales/it/messages.po Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> --- src/locale/locales/it/messages.po | 60 +++++++++++++++---------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/locale/locales/it/messages.po b/src/locale/locales/it/messages.po index e2c2bdb3ac..59660ca2e5 100644 --- a/src/locale/locales/it/messages.po +++ b/src/locale/locales/it/messages.po @@ -165,7 +165,7 @@ msgstr "" #~ msgstr "<0>{following} <1>following" #~ msgid "<0>Choose your<1>Recommended<2>Feeds" -#~ msgstr "<0>Scegli i tuoi<1>feeds<2>consigliati" +#~ msgstr "<0>Scegli i tuoi<1>feed/1><2>consigliati" #~ msgid "<0>Follow some<1>Recommended<2>Users" #~ msgstr "<0>Segui alcuni<1>utenti<2>consigliati" @@ -356,7 +356,7 @@ msgstr "Aggiunto alla lista" #: src/view/com/feeds/FeedSourceCard.tsx:126 msgid "Added to my feeds" -msgstr "Aggiunto ai miei feeds" +msgstr "Aggiunto ai miei feed" #: src/view/screens/PreferencesFollowingFeed.tsx:172 msgid "Adjust the number of likes a reply must have to be shown in your feed." @@ -395,7 +395,7 @@ msgstr "" #: src/screens/Messages/Settings.tsx:62 #: src/screens/Messages/Settings.tsx:65 msgid "Allow new messages from" -msgstr "" +msgstr "Consenti nuovi messaggi da" #: src/screens/Login/ForgotPasswordForm.tsx:178 #: src/view/com/modals/ChangePassword.tsx:171 @@ -936,12 +936,12 @@ msgstr "Conversazione silenziata" #: src/screens/Messages/List/index.tsx:88 #: src/view/screens/Settings/index.tsx:638 msgid "Chat settings" -msgstr "" +msgstr "Impostazioni messaggi" #: src/screens/Messages/Settings.tsx:59 #: src/view/screens/Settings/index.tsx:647 msgid "Chat Settings" -msgstr "" +msgstr "Impostazioni messaggi" #: src/components/dms/ConvoMenu.tsx:84 msgid "Chat unmuted" @@ -1043,7 +1043,7 @@ msgstr "" #: src/screens/Feeds/NoFollowingFeed.tsx:46 #~ msgid "Click here to add one." -#~ msgstr "" +#~ msgstr "Clicca qui per aggiungerne uno." #: src/components/TagMenu/index.web.tsx:138 msgid "Click here to open tag menu for {tag}" @@ -1665,14 +1665,14 @@ msgstr "Scoraggia le app dal mostrare il mio account agli utenti disconnessi" #: src/view/com/posts/FollowingEmptyState.tsx:70 #: src/view/com/posts/FollowingEndOfFeed.tsx:71 msgid "Discover new custom feeds" -msgstr "Scopri nuovi feeds personalizzati" +msgstr "Scopri nuovi feed personalizzati" #~ msgid "Discover new feeds" -#~ msgstr "Scopri nuovi feeds" +#~ msgstr "Scopri nuovi feed" #: src/view/screens/Feeds.tsx:794 msgid "Discover New Feeds" -msgstr "Scopri nuovi feeds" +msgstr "Scopri nuovi feed" #: src/view/com/modals/EditProfile.tsx:193 msgid "Display name" @@ -1831,7 +1831,7 @@ msgstr "Modifica l'elenco di moderazione" #: src/view/screens/Feeds.tsx:469 #: src/view/screens/SavedFeeds.tsx:93 msgid "Edit My Feeds" -msgstr "Modifica i miei feeds" +msgstr "Modifica i miei feed" #: src/view/com/modals/EditProfile.tsx:153 msgid "Edit my profile" @@ -1850,7 +1850,7 @@ msgstr "Modifica il Profilo" #: src/view/com/home/HomeHeaderLayout.web.tsx:76 #: src/view/screens/Feeds.tsx:416 #~ msgid "Edit Saved Feeds" -#~ msgstr "Modifica i feeds memorizzati" +#~ msgstr "Modifica i feed memorizzati" #: src/view/com/modals/CreateOrEditList.tsx:234 msgid "Edit User List" @@ -1927,7 +1927,7 @@ msgstr "Attiva il contenuto per adulti" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:78 #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:79 #~ msgid "Enable adult content in your feeds" -#~ msgstr "Abilita i contenuti per adulti nei tuoi feeds" +#~ msgstr "Abilita i contenuti per adulti nei tuoi feed" #: src/components/dialogs/EmbedConsent.tsx:82 #: src/components/dialogs/EmbedConsent.tsx:89 @@ -2202,7 +2202,7 @@ msgstr "Commenti" #: src/view/shell/Drawer.tsx:493 #: src/view/shell/Drawer.tsx:494 msgid "Feeds" -msgstr "Feeds" +msgstr "Feed" #~ msgid "Feeds are created by users to curate content. Choose some feeds that you find interesting." #~ msgstr "I feed vengono creati dagli utenti per curare i contenuti. Scegli alcuni feed che ritieni interessanti." @@ -2213,7 +2213,7 @@ msgstr "I feed sono algoritmi personalizzati che gli utenti creano con un minimo #: src/screens/Onboarding/StepTopicalFeeds.tsx:80 #~ msgid "Feeds can be topical as well!" -#~ msgstr "I feeds possono anche avere tematiche!" +#~ msgstr "I feed possono anche avere tematiche!" #: src/view/com/modals/ChangeHandle.tsx:475 msgid "File Contents" @@ -3214,7 +3214,7 @@ msgstr "Il messaggio è troppo lungo" #: src/screens/Messages/List/index.tsx:321 msgid "Message settings" -msgstr "Impostazione messaggio" +msgstr "Impostazioni messaggio" #: src/Navigation.tsx:504 #: src/screens/Messages/List/index.tsx:164 @@ -3412,7 +3412,7 @@ msgstr "Il mio Compleanno" #: src/view/screens/Feeds.tsx:768 msgid "My Feeds" -msgstr "I miei Feeds" +msgstr "I miei Feed" #: src/view/shell/desktop/LeftNav.tsx:84 msgid "My Profile" @@ -3424,7 +3424,7 @@ msgstr "I miei feed salvati" #: src/view/screens/Settings/index.tsx:622 msgid "My Saved Feeds" -msgstr "I miei Feeds Salvati" +msgstr "I miei Feed Salvati" #~ msgid "my-server.com" #~ msgstr "my-server.com" @@ -3862,7 +3862,7 @@ msgstr "Apre la fotocamera sul dispositivo" #: src/view/screens/Settings/index.tsx:639 msgid "Opens chat settings" -msgstr "" +msgstr "Apre impostazioni messaggi" #: src/view/com/composer/Prompt.tsx:27 msgid "Opens composer" @@ -4112,7 +4112,7 @@ msgstr "Fissa su Home" #: src/view/screens/SavedFeeds.tsx:103 msgid "Pinned Feeds" -msgstr "Feeds Fissi" +msgstr "Feed Fissi" #: src/view/screens/ProfileList.tsx:289 msgid "Pinned to your feeds" @@ -4380,7 +4380,7 @@ msgstr "Elenchi pubblici e condivisibili di utenti da disattivare o bloccare in #: src/view/screens/Lists.tsx:66 msgid "Public, shareable lists which can drive feeds." -msgstr "Liste pubbliche e condivisibili che possono impulsare i feeds." +msgstr "Liste pubbliche e condivisibili che possono impulsare i feed." #: src/view/com/composer/Composer.tsx:462 msgid "Publish post" @@ -4431,7 +4431,7 @@ msgid "Recent Searches" msgstr "Ricerche recenti" #~ msgid "Recommended Feeds" -#~ msgstr "Feeds consigliati" +#~ msgstr "Feed consigliati" #~ msgid "Recommended Users" #~ msgstr "Utenti consigliati" @@ -4454,7 +4454,7 @@ msgid "Remove" msgstr "Rimuovi" #~ msgid "Remove {0} from my feeds?" -#~ msgstr "Rimuovere {0} dai miei feeds?" +#~ msgstr "Rimuovere {0} dai miei feed?" #: src/view/com/util/AccountDropdownBtn.tsx:22 msgid "Remove account" @@ -4524,14 +4524,14 @@ msgid "Remove repost" msgstr "Rimuovi la ripubblicazione" #~ msgid "Remove this feed from my feeds?" -#~ msgstr "Rimuovere questo feed dai miei feeds?" +#~ msgstr "Rimuovere questo feed dai miei feed?" #: src/view/com/posts/FeedErrorMessage.tsx:210 msgid "Remove this feed from your saved feeds" msgstr "Rimuovi questo feed dai feed salvati" #~ msgid "Remove this feed from your saved feeds?" -#~ msgstr "Elimina questo feed dai feeds salvati?" +#~ msgstr "Elimina questo feed dai feed salvati?" #: src/view/com/modals/ListAddRemoveUsers.tsx:199 #: src/view/com/modals/UserAddRemoveLists.tsx:165 @@ -4540,7 +4540,7 @@ msgstr "Elimina dalla lista" #: src/view/com/feeds/FeedSourceCard.tsx:139 msgid "Removed from my feeds" -msgstr "Rimuovere dai miei feeds" +msgstr "Rimuovere dai miei feed" #: src/view/com/posts/FeedShutdownMsg.tsx:44 #: src/view/screens/ProfileFeed.tsx:191 @@ -5047,7 +5047,7 @@ msgstr "Seleziona il servizio che ospita i tuoi dati." #: src/screens/Onboarding/StepTopicalFeeds.tsx:100 #~ msgid "Select topical feeds to follow from the list below" -#~ msgstr "Seleziona i feeds con temi da seguire dal seguente elenco" +#~ msgstr "Seleziona i feed con temi da seguire dal seguente elenco" #: src/screens/Onboarding/StepModeration/index.tsx:63 #~ msgid "Select what you want to see (or not see), and we’ll handle the rest." @@ -6154,7 +6154,7 @@ msgid "This will delete {0} from your muted words. You can always add it back la msgstr "Questo eliminerà {0} dalle parole disattivate. Puoi sempre aggiungerla nuovamente in seguito." #~ msgid "This will hide this post from your feeds." -#~ msgstr "Questo nasconderà il post dai tuoi feeds." +#~ msgstr "Questo nasconderà il post dai tuoi feed." #: src/view/screens/Settings/index.tsx:594 msgid "Thread preferences" @@ -6783,7 +6783,7 @@ msgstr "Che lingue sono utilizzate in questo post?" #: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:77 msgid "Which languages would you like to see in your algorithmic feeds?" -msgstr "Quali lingue vorresti vedere negli algoritmi dei tuoi feeds?" +msgstr "Quali lingue vorresti vedere negli algoritmi dei tuoi feed?" #: src/components/dms/MessagesNUX.tsx:110 #: src/components/dms/MessagesNUX.tsx:124 @@ -6901,7 +6901,7 @@ msgstr "Puoi modificarlo in qualsiasi momento." #: src/screens/Messages/Settings.tsx:111 msgid "You can continue ongoing conversations regardless of which setting you choose." -msgstr "" +msgstr "Puoi proseguire le conversazioni in corso indipendentemente da quale settaggio scegli." #: src/screens/Login/index.tsx:158 #: src/screens/Login/PasswordUpdatedForm.tsx:33 @@ -6982,7 +6982,7 @@ msgstr "Non hai ancora nessuna conversazione. Avviane una!" #: src/view/com/feeds/ProfileFeedgens.tsx:141 msgid "You have no feeds." -msgstr "Non hai feeds." +msgstr "Non hai feed." #: src/view/com/lists/MyLists.tsx:90 #: src/view/com/lists/ProfileLists.tsx:145 From 8ff862798fdf94ac7d7abbb0111d7f9c6938c7b3 Mon Sep 17 00:00:00 2001 From: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> Date: Fri, 21 Jun 2024 07:49:50 +0900 Subject: [PATCH 082/127] Update Japanese translation (#4477) * Update Japanese translation * Update Japanese translation * Fix translation ref. https://github.com/bluesky-social/social-app/pull/4477#pullrequestreview-2114478514 * Update Japanese translation * Fix translation * Updated Japanese translation * Updated translations --- src/locale/locales/ja/messages.po | 201 ++++++++++++++++++++++-------- 1 file changed, 152 insertions(+), 49 deletions(-) diff --git a/src/locale/locales/ja/messages.po b/src/locale/locales/ja/messages.po index e8dc12ce24..bb41c63317 100644 --- a/src/locale/locales/ja/messages.po +++ b/src/locale/locales/ja/messages.po @@ -8,7 +8,7 @@ msgstr "" "Language: ja\n" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2024-06-05 11:06+0900\n" +"PO-Revision-Date: 2024-06-19 11:10+0900\n" "Last-Translator: tkusano\n" "Language-Team: Hima-Zinn, tkusano, dolciss, oboenikui, noritada, middlingphys, hibiki, reindex-ot, haoyayoi, vyv03354\n" "Plural-Forms: \n" @@ -37,10 +37,6 @@ msgstr "{0, plural, other {#個のラベルがこのコンテンツに適用さ msgid "{0, plural, one {# repost} other {# reposts}}" msgstr "{0, plural, other {#回のリポスト}}" -#: src/components/KnownFollowers.tsx:179 -msgid "{0, plural, one {and # other} other {and # others}}" -msgstr "" - #: src/components/ProfileHoverCard/index.web.tsx:376 #: src/screens/Profile/Header/Metrics.tsx:23 msgid "{0, plural, one {follower} other {followers}}" @@ -87,6 +83,26 @@ msgstr "{0}のアバター" msgid "{count, plural, one {Liked by # user} other {Liked by # users}}" msgstr "{count, plural, other {#人のユーザーがいいね}}" +#: src/lib/hooks/useTimeAgo.ts:69 +msgid "{diff, plural, one {day} other {days}}" +msgstr "{diff, plural, other {日}}" + +#: src/lib/hooks/useTimeAgo.ts:64 +msgid "{diff, plural, one {hour} other {hours}}" +msgstr "{diff, plural, other {時間}}" + +#: src/lib/hooks/useTimeAgo.ts:59 +msgid "{diff, plural, one {minute} other {minutes}}" +msgstr "{diff, plural, other {分}}" + +#: src/lib/hooks/useTimeAgo.ts:75 +msgid "{diff, plural, one {month} other {months}}" +msgstr "{diff, plural, other {ヶ月}}" + +#: src/lib/hooks/useTimeAgo.ts:54 +msgid "{diffSeconds, plural, one {second} other {seconds}}" +msgstr "{diffSeconds, plural, other {秒}}" + #: src/screens/SignupQueued.tsx:207 msgid "{estimatedTimeHrs, plural, one {hour} other {hours}}" msgstr "{estimatedTimeHrs, plural, other {時間}}" @@ -114,6 +130,10 @@ msgstr "{likeCount, plural, other {#人のユーザーがいいね}}" msgid "{numUnreadNotifications} unread" msgstr "{numUnreadNotifications}件の未読" +#: src/components/NewskieDialog.tsx:75 +msgid "{profileName} joined Bluesky {0} ago" +msgstr "{profileName}はBlueskyに{0}前に参加しました" + #: src/view/screens/PreferencesFollowingFeed.tsx:67 msgid "{value, plural, =0 {Show all replies} one {Show replies with at least # like} other {Show replies with at least # likes}}" msgstr "{value, plural, =0 {すべての返信を表示} other {#個以上のいいねがついた返信を表示}}" @@ -270,6 +290,10 @@ msgstr "フォローしているユーザーのみのデフォルトのフィー msgid "Add the following DNS record to your domain:" msgstr "次のDNSレコードをドメインに追加してください:" +#: src/components/FeedCard.tsx:173 +msgid "Add this feed to your feeds" +msgstr "このフィードをあなたのフィードに追加する" + #: src/view/com/profile/ProfileMenu.tsx:265 #: src/view/com/profile/ProfileMenu.tsx:268 msgid "Add to Lists" @@ -469,6 +493,10 @@ msgstr "この会話から退出しますか?あなたのメッセージはあ msgid "Are you sure you want to remove {0} from your feeds?" msgstr "あなたのフィードから{0}を削除してもよろしいですか?" +#: src/components/FeedCard.tsx:190 +msgid "Are you sure you want to remove this from your feeds?" +msgstr "本当にこのフィードをあなたのフィードから削除したいですか?" + #: src/view/com/composer/Composer.tsx:630 msgid "Are you sure you'd like to discard this draft?" msgstr "本当にこの下書きを破棄しますか?" @@ -1092,7 +1120,7 @@ msgstr "{0}として続行(現在サインイン中)" #: src/view/com/post-thread/PostThreadLoadMore.tsx:52 msgid "Continue thread..." -msgstr "" +msgstr "スレッドの続き…" #: src/screens/Onboarding/StepInterests/index.tsx:250 #: src/screens/Onboarding/StepProfile/index.tsx:266 @@ -1419,6 +1447,10 @@ msgstr "アプリがログアウトしたユーザーに自分のアカウント msgid "Discover new custom feeds" msgstr "新しいカスタムフィードを見つける" +#: src/view/screens/Search/Explore.tsx:378 +msgid "Discover new feeds" +msgstr "新しいフィードを探す" + #: src/view/screens/Feeds.tsx:794 msgid "Discover New Feeds" msgstr "新しいフィードを探す" @@ -1534,16 +1566,16 @@ msgstr "例:返信として広告を繰り返し送ってくるユーザー。 msgid "Each code works once. You'll receive more invite codes periodically." msgstr "それぞれのコードは一回限り有効です。定期的に追加の招待コードをお送りします。" -#: src/view/screens/Feeds.tsx:400 -#: src/view/screens/Feeds.tsx:471 -msgid "Edit" -msgstr "" - #: src/view/com/lists/ListMembers.tsx:149 msgctxt "action" msgid "Edit" msgstr "編集" +#: src/view/screens/Feeds.tsx:400 +#: src/view/screens/Feeds.tsx:471 +msgid "Edit" +msgstr "編集" + #: src/view/com/util/UserAvatar.tsx:312 #: src/view/com/util/UserBanner.tsx:92 msgid "Edit avatar" @@ -1583,11 +1615,6 @@ msgstr "プロフィールを編集" msgid "Edit Profile" msgstr "プロフィールを編集" -#: src/view/com/home/HomeHeaderLayout.web.tsx:76 -#: src/view/screens/Feeds.tsx:416 -#~ msgid "Edit Saved Feeds" -#~ msgstr "保存されたフィードを編集" - #: src/view/com/modals/CreateOrEditList.tsx:234 msgid "Edit User List" msgstr "ユーザーリストを編集" @@ -1754,6 +1781,10 @@ msgstr "全員" msgid "Everybody can reply" msgstr "誰でも返信可能" +#: src/view/com/threadgate/WhoCanReply.tsx:129 +msgid "Everybody can reply." +msgstr "誰でも返信可能です。" + #: src/components/dms/MessagesNUX.tsx:131 #: src/components/dms/MessagesNUX.tsx:134 #: src/screens/Messages/Settings.tsx:75 @@ -1857,6 +1888,11 @@ msgstr "メッセージの削除に失敗しました" msgid "Failed to delete post, please try again" msgstr "投稿の削除に失敗しました。もう一度お試しください。" +#: src/view/screens/Search/Explore.tsx:414 +#: src/view/screens/Search/Explore.tsx:438 +msgid "Failed to load feeds preferences" +msgstr "フィードの設定の読み込みに失敗しました" + #: src/components/dialogs/GifSelect.ios.tsx:196 #: src/components/dialogs/GifSelect.tsx:212 msgid "Failed to load GIFs" @@ -1866,6 +1902,15 @@ msgstr "GIFの読み込みに失敗しました" msgid "Failed to load past messages" msgstr "過去のメッセージの読み込みに失敗しました" +#: src/view/screens/Search/Explore.tsx:407 +#: src/view/screens/Search/Explore.tsx:431 +msgid "Failed to load suggested feeds" +msgstr "おすすめのフィードの読み込みに失敗しました" + +#: src/view/screens/Search/Explore.tsx:367 +msgid "Failed to load suggested follows" +msgstr "おすすめのフォローの読み込みに失敗しました" + #: src/view/com/lightbox/Lightbox.tsx:84 msgid "Failed to save image: {0}" msgstr "画像の保存に失敗しました:{0}" @@ -1879,6 +1924,14 @@ msgstr "送信に失敗" msgid "Failed to submit appeal, please try again." msgstr "異議申し立ての送信に失敗しました。再度試してください。" +#: src/view/com/util/forms/PostDropdownBtn.tsx:180 +msgid "Failed to toggle thread mute, please try again" +msgstr "スレッドのミュートの切り替えに失敗しました。再度試してください" + +#: src/components/FeedCard.tsx:153 +msgid "Failed to update feeds" +msgstr "フィードの更新に失敗しました" + #: src/components/dms/MessagesNUX.tsx:60 #: src/screens/Messages/Settings.tsx:35 msgid "Failed to update settings" @@ -1914,6 +1967,10 @@ msgstr "フィード" msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." msgstr "フィードはユーザーがプログラミングの専門知識を持って構築するカスタムアルゴリズムです。詳細については、<0/>を参照してください。" +#: src/components/FeedCard.tsx:150 +msgid "Feeds updated!" +msgstr "フィードを更新しました!" + #: src/view/com/modals/ChangeHandle.tsx:475 msgid "File Contents" msgstr "ファイルのコンテンツ" @@ -1996,14 +2053,30 @@ msgstr "アカウントをフォロー" msgid "Follow Back" msgstr "フォローバック" -#: src/components/KnownFollowers.tsx:169 -msgid "Followed by" -msgstr "" +#: src/view/screens/Search/Explore.tsx:332 +msgid "Follow more accounts to get connected to your interests and build your network." +msgstr "もっとたくさんのアカウントをフォローして、興味あることにつながり、ネットワークを広げましょう。" #: src/view/com/profile/ProfileCard.tsx:227 msgid "Followed by {0}" msgstr "{0}がフォロー中" +#: src/components/KnownFollowers.tsx:192 +msgid "Followed by <0>{0}" +msgstr "<0>{0}がフォロー中" + +#: src/components/KnownFollowers.tsx:209 +msgid "Followed by <0>{0} and {1, plural, one {# other} other {# others}}" +msgstr "<0>{0}および{1, plural, other {他#人}}がフォロー中" + +#: src/components/KnownFollowers.tsx:181 +msgid "Followed by <0>{0} and <1>{1}" +msgstr "<0>{0}と<1>{1}がフォロー中" + +#: src/components/KnownFollowers.tsx:168 +msgid "Followed by <0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}}" +msgstr "<0>{0}、<1>{1}および{2, plural, other {他#人}}がフォロー中" + #: src/view/com/modals/Threadgate.tsx:99 msgid "Followed users" msgstr "自分がフォローしているユーザー" @@ -2023,12 +2096,12 @@ msgstr "フォロワー" #: src/Navigation.tsx:177 msgid "Followers of @{0} that you know" -msgstr "" +msgstr "あなたが知っている@{0}のフォロワー" #: src/screens/Profile/KnownFollowers.tsx:108 #: src/screens/Profile/KnownFollowers.tsx:118 msgid "Followers you know" -msgstr "" +msgstr "あなたが知っているフォロワー" #: src/components/ProfileHoverCard/index.web.tsx:411 #: src/components/ProfileHoverCard/index.web.tsx:422 @@ -2118,6 +2191,10 @@ msgstr "始める" msgid "Get Started" msgstr "開始" +#: src/view/com/util/images/ImageHorzList.tsx:35 +msgid "GIF" +msgstr "GIF" + #: src/screens/Onboarding/StepProfile/index.tsx:225 msgid "Give your profile a face" msgstr "プロフィールに顔をつける" @@ -2658,6 +2735,18 @@ msgstr "リスト" msgid "Lists blocking this user:" msgstr "このユーザーをブロックしているリスト:" +#: src/view/screens/Search/Explore.tsx:128 +msgid "Load more" +msgstr "さらに読み込む" + +#: src/view/screens/Search/Explore.tsx:216 +msgid "Load more suggested feeds" +msgstr "おすすめのフィードをさらに読み込む" + +#: src/view/screens/Search/Explore.tsx:214 +msgid "Load more suggested follows" +msgstr "おすすめのフォローをさらに読み込む" + #: src/view/screens/Notifications.tsx:184 msgid "Load new notifications" msgstr "最新の通知を読み込む" @@ -3062,6 +3151,10 @@ msgctxt "action" msgid "New Post" msgstr "新しい投稿" +#: src/components/NewskieDialog.tsx:68 +msgid "New user info dialog" +msgstr "新しいユーザー情報ダイアログ" + #: src/view/com/modals/CreateOrEditList.tsx:236 msgid "New User List" msgstr "新しいユーザーリスト" @@ -3142,7 +3235,7 @@ msgstr "誰からも受け取らない" #: src/screens/Profile/Sections/Feed.tsx:59 msgid "No posts yet." -msgstr "" +msgstr "まだ投稿がありません。" #: src/view/com/composer/text-input/mobile/Autocomplete.tsx:101 #: src/view/com/composer/text-input/web/Autocomplete.tsx:195 @@ -3236,6 +3329,10 @@ msgstr "通知音" msgid "Notifications" msgstr "通知" +#: src/lib/hooks/useTimeAgo.ts:51 +msgid "now" +msgstr "今" + #: src/components/dms/MessageItem.tsx:175 msgid "Now" msgstr "今" @@ -3274,6 +3371,10 @@ msgstr "OK" msgid "Oldest replies first" msgstr "古い順に返信を表示" +#: src/lib/hooks/useTimeAgo.ts:81 +msgid "on {str}" +msgstr "{str}" + #: src/view/screens/Settings/index.tsx:256 msgid "Onboarding reset" msgstr "オンボーディングのリセット" @@ -3449,11 +3550,6 @@ msgstr "モデレーションの設定を開く" msgid "Opens password reset form" msgstr "パスワードリセットのフォームを開く" -#: src/view/com/home/HomeHeaderLayout.web.tsx:77 -#: src/view/screens/Feeds.tsx:417 -#~ msgid "Opens screen to edit Saved Feeds" -#~ msgstr "保存されたフィードの編集画面を開く" - #: src/view/screens/Settings/index.tsx:617 msgid "Opens screen with all saved feeds" msgstr "保存されたすべてのフィードで画面を開く" @@ -3777,7 +3873,7 @@ msgstr "再実行する" #: src/components/KnownFollowers.tsx:111 msgid "Press to view followers of this account that you also follow" -msgstr "" +msgstr "あなたもフォローしているこのアカウントのフォロワーを見る" #: src/view/com/lightbox/Lightbox.web.tsx:150 msgid "Previous image" @@ -3975,7 +4071,7 @@ msgstr "リストから削除されました" #: src/view/com/feeds/FeedSourceCard.tsx:139 msgid "Removed from my feeds" -msgstr "フィードから削除しました" +msgstr "マイフィードから削除しました" #: src/view/com/posts/FeedShutdownMsg.tsx:44 #: src/view/screens/ProfileFeed.tsx:191 @@ -4000,9 +4096,9 @@ msgstr "Discoverで置き換える" msgid "Replies" msgstr "返信" -#: src/view/com/threadgate/WhoCanReply.tsx:98 -msgid "Replies to this thread are disabled" -msgstr "このスレッドへの返信はできません" +#: src/view/com/threadgate/WhoCanReply.tsx:131 +msgid "Replies to this thread are disabled." +msgstr "このスレッドへの返信はできません。" #: src/view/com/composer/Composer.tsx:475 msgctxt "action" @@ -4019,6 +4115,11 @@ msgctxt "description" msgid "Reply to <0><1/>" msgstr "<0><1/>に返信" +#: src/view/com/posts/FeedItem.tsx:437 +msgctxt "description" +msgid "Reply to a blocked post" +msgstr "ブロックした投稿への返信" + #: src/components/dms/MessageMenu.tsx:132 #: src/components/dms/MessagesListBlockedFooter.tsx:77 #: src/components/dms/MessagesListBlockedFooter.tsx:84 @@ -4926,9 +5027,9 @@ msgstr "このラベラーを登録" msgid "Subscribe to this list" msgstr "このリストに登録" -#: src/view/screens/Search/Search.tsx:425 -msgid "Suggested Follows" -msgstr "おすすめのフォロー" +#: src/view/screens/Search/Explore.tsx:330 +msgid "Suggested accounts" +msgstr "おすすめのアカウント" #: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:65 msgid "Suggested for you" @@ -5077,7 +5178,7 @@ msgstr "サービス規約は移動しました" #: src/screens/Settings/components/DeactivateAccountDialog.tsx:86 msgid "There is no time limit for account deactivation, come back any time." -msgstr "アカウントの無効化に期限はありません。いつでも戻ってこれます。" +msgstr "アカウントの無効化に期限はありません。いつでも戻ってこられます。" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:115 #: src/view/screens/ProfileFeed.tsx:541 @@ -5217,7 +5318,7 @@ msgstr "このコンテンツはBlueskyのアカウントがないと閲覧で #: src/screens/Messages/List/ChatListItem.tsx:213 msgid "This conversation is with a deleted or a deactivated account. Press for options." -msgstr "" +msgstr "削除あるいは無効化されたアカウントとの会話です。押すと選択肢が表示されます。" #: src/view/screens/Settings/ExportCarDialog.tsx:93 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost." @@ -5227,12 +5328,6 @@ msgstr "この機能はベータ版です。リポジトリのエクスポート msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." msgstr "現在このフィードにはアクセスが集中しており、一時的にご利用いただけません。時間をおいてもう一度お試しください。" -#: src/screens/Profile/Sections/Feed.tsx:59 -#: src/view/screens/ProfileFeed.tsx:471 -#: src/view/screens/ProfileList.tsx:729 -#~ msgid "This feed is empty!" -#~ msgstr "このフィードは空です!" - #: src/view/com/posts/CustomFeedEmptyState.tsx:37 msgid "This feed is empty! You may need to follow more users or tune your language settings." msgstr "このフィードは空です!もっと多くのユーザーをフォローするか、言語の設定を調整する必要があるかもしれません。" @@ -5240,7 +5335,7 @@ msgstr "このフィードは空です!もっと多くのユーザーをフォ #: src/view/screens/ProfileFeed.tsx:471 #: src/view/screens/ProfileList.tsx:729 msgid "This feed is empty." -msgstr "" +msgstr "このフィードは空です。" #: src/view/com/posts/FeedShutdownMsg.tsx:97 msgid "This feed is no longer online. We are showing <0>Discover instead." @@ -5336,6 +5431,10 @@ msgstr "このユーザーはブロックした<0>{0}リストに含まれ msgid "This user is included in the <0>{0} list which you have muted." msgstr "このユーザーはミュートした<0>{0}リストに含まれています。" +#: src/components/NewskieDialog.tsx:50 +msgid "This user is new here. Press for more info about when they joined." +msgstr "新しいユーザーです。ここを押すといつ参加したかの情報が表示されます。" + #: src/view/com/profile/ProfileFollows.tsx:87 msgid "This user isn't following anyone." msgstr "このユーザーは誰もフォローしていません。" @@ -5762,6 +5861,10 @@ msgstr "{0}のアバターを表示" msgid "View {0}'s profile" msgstr "{0}のプロフィールを表示" +#: src/components/ProfileHoverCard/index.web.tsx:417 +msgid "View blocked user's profile" +msgstr "ブロック中のユーザーのプロフィールを表示" + #: src/view/screens/Log.tsx:52 msgid "View debug entry" msgstr "デバッグエントリーを表示" @@ -5804,7 +5907,7 @@ msgstr "このフィードにいいねしたユーザーを見る" #: src/view/com/home/HomeHeaderLayout.web.tsx:78 #: src/view/com/home/HomeHeaderLayoutMobile.tsx:84 msgid "View your feeds and explore more" -msgstr "" +msgstr "フィードを表示し、さらにフィードを探す" #: src/view/com/modals/LinkWarning.tsx:89 #: src/view/com/modals/LinkWarning.tsx:95 @@ -5891,7 +5994,7 @@ msgstr "大変申し訳ありませんが、検索を完了できませんでし #: src/view/com/composer/Composer.tsx:318 msgid "We're sorry! The post you are replying to has been deleted." -msgstr "" +msgstr "大変申し訳ありません!返信しようとしている投稿は削除されました。" #: src/components/Lists.tsx:212 #: src/view/screens/NotFound.tsx:48 @@ -5899,8 +6002,8 @@ msgid "We're sorry! We can't find the page you were looking for." msgstr "大変申し訳ありません!お探しのページは見つかりません。" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:330 -msgid "We're sorry! You can only subscribe to ten labelers, and you've reached your limit of ten." -msgstr "大変申し訳ありません!ラベラーは10までしか登録できず、すでに上限に達しています。" +msgid "We're sorry! You can only subscribe to twenty labelers, and you've reached your limit of twenty." +msgstr "大変申し訳ありません!ラベラーは20までしか登録できず、すでに上限に達しています。" #: src/screens/Deactivated.tsx:128 msgid "Welcome back!" @@ -6047,7 +6150,7 @@ msgstr "あなたはまだだれもフォロワーがいません。" #: src/screens/Profile/KnownFollowers.tsx:99 msgid "You don't follow any users who follow @{name}." -msgstr "" +msgstr "@{name}をフォローしているユーザーを誰もフォローしていません。" #: 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." From ba21fddd7897513fef663b826094878ad0ff1556 Mon Sep 17 00:00:00 2001 From: Minseo Lee Date: Fri, 21 Jun 2024 07:50:18 +0900 Subject: [PATCH 083/127] Update Korean localization (#4513) * Update messages.po * Update messages.po * Update messages.po * Update messages.po * Update messages.po * Update messages.po * Update messages.po * Update messages.po * Update messages.po * Update messages.po --- src/locale/locales/ko/messages.po | 767 ++++++++++++++++++------------ 1 file changed, 456 insertions(+), 311 deletions(-) diff --git a/src/locale/locales/ko/messages.po b/src/locale/locales/ko/messages.po index ea3088188c..5f36a7eb7f 100644 --- a/src/locale/locales/ko/messages.po +++ b/src/locale/locales/ko/messages.po @@ -8,7 +8,7 @@ msgstr "" "Language: ko\n" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2024-06-08 15:32+0900\n" +"PO-Revision-Date: 2024-06-19 10:55+0900\n" "Last-Translator: quiple\n" "Language-Team: quiple, lens0021, HaruChanHeart, hazzzi, heartade\n" "Plural-Forms: \n" @@ -21,7 +21,7 @@ msgstr "(임베드 콘텐츠 포함)" msgid "(no email)" msgstr "(이메일 없음)" -#: src/view/com/notifications/FeedItem.tsx:261 +#: src/view/com/notifications/FeedItem.tsx:263 msgid "{0, plural, one {{formattedCount} other} other {{formattedCount} others}}" msgstr "외 {0, plural, other {{formattedCount}}}명" @@ -33,32 +33,29 @@ msgstr "이 계정에 {0, plural, other {#}}개의 라벨이 지정됨" msgid "{0, plural, one {# label has been placed on this content} other {# labels have been placed on this content}}" msgstr "이 콘텐츠에 {0, plural, other {#}}개의 라벨이 지정됨" -#: src/view/com/util/post-ctrls/RepostButton.tsx:65 +#: src/view/com/util/post-ctrls/RepostButton.tsx:66 msgid "{0, plural, one {# repost} other {# reposts}}" msgstr "{0, plural, other {#}}개" -#: src/components/KnownFollowers.tsx:179 -msgid "{0, plural, one {and # other} other {and # others}}" -msgstr "" - -#: src/components/ProfileHoverCard/index.web.tsx:376 +#: src/components/ProfileHoverCard/index.web.tsx:398 #: src/screens/Profile/Header/Metrics.tsx:23 msgid "{0, plural, one {follower} other {followers}}" msgstr "팔로워" -#: src/components/ProfileHoverCard/index.web.tsx:380 +#: src/components/ProfileHoverCard/index.web.tsx:402 #: src/screens/Profile/Header/Metrics.tsx:27 msgid "{0, plural, one {following} other {following}}" msgstr "팔로우 중" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:252 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:255 msgid "{0, plural, one {Like (# like)} other {Like (# likes)}}" msgstr "좋아요 ({0, plural, other {#}}개)" -#: src/view/com/post-thread/PostThreadItem.tsx:380 +#: src/view/com/post-thread/PostThreadItem.tsx:382 msgid "{0, plural, one {like} other {likes}}" msgstr "좋아요" +#: src/components/FeedCard.tsx:111 #: src/view/com/feeds/FeedSourceCard.tsx:301 msgid "{0, plural, one {Liked by # user} other {Liked by # users}}" msgstr "{0, plural, other {#}}명의 사용자가 좋아함" @@ -67,19 +64,19 @@ msgstr "{0, plural, other {#}}명의 사용자가 좋아함" msgid "{0, plural, one {post} other {posts}}" msgstr "게시물" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:210 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:213 msgid "{0, plural, one {Reply (# reply)} other {Reply (# replies)}}" msgstr "답글 ({0, plural, other {#}}개)" -#: src/view/com/post-thread/PostThreadItem.tsx:360 +#: src/view/com/post-thread/PostThreadItem.tsx:362 msgid "{0, plural, one {repost} other {reposts}}" msgstr "재게시" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:248 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:251 msgid "{0, plural, one {Unlike (# like)} other {Unlike (# likes)}}" msgstr "좋아요 취소 ({0, plural, other {#}}개)" -#: src/view/com/util/UserAvatar.tsx:406 +#: src/view/com/util/UserAvatar.tsx:419 msgid "{0}'s avatar" msgstr "{0} 님의 아바타" @@ -87,6 +84,26 @@ msgstr "{0} 님의 아바타" msgid "{count, plural, one {Liked by # user} other {Liked by # users}}" msgstr "{count, plural, other {#}}명의 사용자가 좋아함" +#: src/lib/hooks/useTimeAgo.ts:69 +msgid "{diff, plural, one {day} other {days}}" +msgstr "일" + +#: src/lib/hooks/useTimeAgo.ts:64 +msgid "{diff, plural, one {hour} other {hours}}" +msgstr "시간" + +#: src/lib/hooks/useTimeAgo.ts:59 +msgid "{diff, plural, one {minute} other {minutes}}" +msgstr "분" + +#: src/lib/hooks/useTimeAgo.ts:75 +msgid "{diff, plural, one {month} other {months}}" +msgstr "개월" + +#: src/lib/hooks/useTimeAgo.ts:54 +msgid "{diffSeconds, plural, one {second} other {seconds}}" +msgstr "초" + #: src/screens/SignupQueued.tsx:207 msgid "{estimatedTimeHrs, plural, one {hour} other {hours}}" msgstr "시간" @@ -95,7 +112,7 @@ msgstr "시간" msgid "{estimatedTimeMins, plural, one {minute} other {minutes}}" msgstr "분" -#: src/components/ProfileHoverCard/index.web.tsx:457 +#: src/components/ProfileHoverCard/index.web.tsx:503 #: src/screens/Profile/Header/Metrics.tsx:50 msgid "{following} following" msgstr "{following} 팔로우 중" @@ -114,11 +131,15 @@ msgstr "{likeCount, plural, other {#}}명의 사용자가 좋아함" msgid "{numUnreadNotifications} unread" msgstr "{numUnreadNotifications}개 읽지 않음" +#: src/components/NewskieDialog.tsx:75 +msgid "{profileName} joined Bluesky {0} ago" +msgstr "{profileName} 님은 {0} 전에 Bluesky에 가입했습니다." + #: src/view/screens/PreferencesFollowingFeed.tsx:67 msgid "{value, plural, =0 {Show all replies} one {Show replies with at least # like} other {Show replies with at least # likes}}" msgstr "{value, plural, =0 {모든 답글 표시} other {좋아요가 #개 이상인 답글 표시}}" -#: src/view/com/threadgate/WhoCanReply.tsx:159 +#: src/view/com/threadgate/WhoCanReply.tsx:290 msgid "<0/> members" msgstr "<0/>의 멤버" @@ -134,7 +155,7 @@ msgstr "<0>{0} 팔로우 중" msgid "<0>Not Applicable. This warning is only available for posts with media attached." msgstr "<0>해당 없음. 이 경고는 미디어가 첨부된 게시물에만 사용할 수 있습니다." -#: src/screens/Profile/Header/Handle.tsx:43 +#: src/screens/Profile/Header/Handle.tsx:50 msgid "⚠Invalid Handle" msgstr "⚠잘못된 핸들" @@ -143,7 +164,7 @@ msgid "2FA Confirmation" msgstr "2단계 인증" #: src/view/com/util/ViewHeader.tsx:93 -#: src/view/screens/Search/Search.tsx:715 +#: src/view/screens/Search/Search.tsx:684 msgid "Access navigation links and settings" msgstr "탐색 링크 및 설정으로 이동합니다" @@ -161,7 +182,7 @@ msgid "Accessibility settings" msgstr "접근성 설정" #: src/Navigation.tsx:296 -#: src/view/screens/AccessibilitySettings.tsx:63 +#: src/view/screens/AccessibilitySettings.tsx:69 msgid "Accessibility Settings" msgstr "접근성 설정" @@ -171,15 +192,15 @@ msgstr "접근성 설정" msgid "Account" msgstr "계정" -#: src/view/com/profile/ProfileMenu.tsx:142 +#: src/view/com/profile/ProfileMenu.tsx:145 msgid "Account blocked" msgstr "계정 차단됨" -#: src/view/com/profile/ProfileMenu.tsx:156 +#: src/view/com/profile/ProfileMenu.tsx:159 msgid "Account followed" msgstr "계정 팔로우함" -#: src/view/com/profile/ProfileMenu.tsx:116 +#: src/view/com/profile/ProfileMenu.tsx:119 msgid "Account muted" msgstr "계정 뮤트됨" @@ -200,16 +221,16 @@ msgstr "계정 옵션" msgid "Account removed from quick access" msgstr "빠른 액세스에서 계정 제거" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:135 -#: src/view/com/profile/ProfileMenu.tsx:131 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:139 +#: src/view/com/profile/ProfileMenu.tsx:134 msgid "Account unblocked" msgstr "계정 차단 해제됨" -#: src/view/com/profile/ProfileMenu.tsx:169 +#: src/view/com/profile/ProfileMenu.tsx:172 msgid "Account unfollowed" msgstr "계정 언팔로우함" -#: src/view/com/profile/ProfileMenu.tsx:105 +#: src/view/com/profile/ProfileMenu.tsx:108 msgid "Account unmuted" msgstr "계정 언뮤트됨" @@ -270,8 +291,12 @@ msgstr "내가 팔로우하는 사람의 기본 피드만 추가하기" msgid "Add the following DNS record to your domain:" msgstr "도메인에 다음 DNS 레코드를 추가하세요:" -#: src/view/com/profile/ProfileMenu.tsx:265 +#: src/components/FeedCard.tsx:180 +msgid "Add this feed to your feeds" +msgstr "이 피드를 내 피드에 추가하기" + #: src/view/com/profile/ProfileMenu.tsx:268 +#: src/view/com/profile/ProfileMenu.tsx:271 msgid "Add to Lists" msgstr "리스트에 추가" @@ -306,7 +331,7 @@ msgstr "성인 콘텐츠가 비활성화되어 있습니다." msgid "Advanced" msgstr "고급" -#: src/view/screens/Feeds.tsx:771 +#: src/view/screens/Feeds.tsx:737 msgid "All the feeds you've saved, right in one place." msgstr "저장한 모든 피드를 한 곳에서 확인하세요." @@ -331,17 +356,17 @@ msgstr "이미 @{0}(으)로 로그인했습니다" #: src/view/com/composer/GifAltText.tsx:93 #: src/view/com/composer/photos/Gallery.tsx:144 -#: src/view/com/util/post-embeds/GifEmbed.tsx:173 +#: src/view/com/util/post-embeds/GifEmbed.tsx:177 msgid "ALT" msgstr "ALT" #: src/view/com/composer/GifAltText.tsx:144 #: src/view/com/modals/EditImage.tsx:316 -#: src/view/screens/AccessibilitySettings.tsx:77 +#: src/view/screens/AccessibilitySettings.tsx:83 msgid "Alt text" msgstr "대체 텍스트" -#: src/view/com/util/post-embeds/GifEmbed.tsx:179 +#: src/view/com/util/post-embeds/GifEmbed.tsx:183 msgid "Alt Text" msgstr "대체 텍스트" @@ -379,9 +404,8 @@ msgstr "문제가 발생했습니다. 다시 시도해 주세요." msgid "an unknown error occurred" msgstr "알 수 없는 오류가 발생했습니다" -#: src/components/KnownFollowers.tsx:187 -#: src/view/com/notifications/FeedItem.tsx:258 -#: src/view/com/threadgate/WhoCanReply.tsx:180 +#: src/view/com/notifications/FeedItem.tsx:260 +#: src/view/com/threadgate/WhoCanReply.tsx:311 msgid "and" msgstr "및" @@ -389,7 +413,7 @@ msgstr "및" msgid "Animals" msgstr "동물" -#: src/view/com/util/post-embeds/GifEmbed.tsx:148 +#: src/view/com/util/post-embeds/GifEmbed.tsx:149 msgid "Animated GIF" msgstr "움직이는 GIF" @@ -469,7 +493,11 @@ msgstr "정말 이 대화에서 나가시겠습니까? 나에게 보이는 메 msgid "Are you sure you want to remove {0} from your feeds?" msgstr "피드에서 {0}을(를) 제거하시겠습니까?" -#: src/view/com/composer/Composer.tsx:630 +#: src/components/FeedCard.tsx:197 +msgid "Are you sure you want to remove this from your feeds?" +msgstr "내 피드에서 이 피드를 삭제하시겠습니까?" + +#: src/view/com/composer/Composer.tsx:632 msgid "Are you sure you'd like to discard this draft?" msgstr "이 초안을 삭제하시겠습니까?" @@ -524,8 +552,8 @@ msgstr "생년월일" msgid "Birthday:" msgstr "생년월일:" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:309 -#: src/view/com/profile/ProfileMenu.tsx:363 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:314 +#: src/view/com/profile/ProfileMenu.tsx:366 msgid "Block" msgstr "차단" @@ -534,12 +562,12 @@ msgstr "차단" msgid "Block account" msgstr "계정 차단" -#: src/view/com/profile/ProfileMenu.tsx:302 -#: src/view/com/profile/ProfileMenu.tsx:309 +#: src/view/com/profile/ProfileMenu.tsx:305 +#: src/view/com/profile/ProfileMenu.tsx:312 msgid "Block Account" msgstr "계정 차단" -#: src/view/com/profile/ProfileMenu.tsx:346 +#: src/view/com/profile/ProfileMenu.tsx:349 msgid "Block Account?" msgstr "계정을 차단하시겠습니까?" @@ -569,7 +597,7 @@ msgstr "차단한 계정" msgid "Blocked Accounts" msgstr "차단한 계정" -#: src/view/com/profile/ProfileMenu.tsx:358 +#: src/view/com/profile/ProfileMenu.tsx:361 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." msgstr "차단한 계정은 내 스레드에 답글을 달거나 나를 멘션하거나 기타 다른 방식으로 나와 상호작용할 수 없습니다." @@ -577,7 +605,7 @@ msgstr "차단한 계정은 내 스레드에 답글을 달거나 나를 멘션 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours." msgstr "차단한 계정은 내 스레드에 답글을 달거나 나를 멘션하거나 기타 다른 방식으로 나와 상호작용할 수 없습니다. 차단한 계정의 콘텐츠를 볼 수 없으며 해당 계정도 내 콘텐츠를 볼 수 없게 됩니다." -#: src/view/com/post-thread/PostThread.tsx:363 +#: src/view/com/post-thread/PostThread.tsx:367 msgid "Blocked post." msgstr "차단된 게시물." @@ -589,7 +617,7 @@ msgstr "차단하더라도 이 라벨러가 내 계정에 라벨을 붙이는 msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." msgstr "차단 목록은 공개됩니다. 차단한 계정은 내 스레드에 답글을 달거나 나를 멘션하거나 기타 다른 방식으로 나와 상호작용할 수 없습니다." -#: src/view/com/profile/ProfileMenu.tsx:355 +#: src/view/com/profile/ProfileMenu.tsx:358 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 "차단하더라도 내 계정에 라벨이 붙는 것은 막지 못하지만, 이 계정이 내 스레드에 답글을 달거나 나와 상호작용하는 것은 중지됩니다." @@ -664,8 +692,8 @@ msgstr "글자, 숫자, 공백, 대시, 밑줄만 포함할 수 있습니다. #: src/components/Prompt.tsx:121 #: src/components/TagMenu/index.tsx:268 #: src/screens/Deactivated.tsx:161 -#: src/view/com/composer/Composer.tsx:432 -#: src/view/com/composer/Composer.tsx:438 +#: src/view/com/composer/Composer.tsx:434 +#: src/view/com/composer/Composer.tsx:440 #: src/view/com/modals/ChangeEmail.tsx:213 #: src/view/com/modals/ChangeEmail.tsx:215 #: src/view/com/modals/ChangeHandle.tsx:148 @@ -681,8 +709,8 @@ msgstr "글자, 숫자, 공백, 대시, 밑줄만 포함할 수 있습니다. #: src/view/com/modals/LinkWarning.tsx:107 #: src/view/com/modals/VerifyEmail.tsx:255 #: src/view/com/modals/VerifyEmail.tsx:261 -#: src/view/com/util/post-ctrls/RepostButton.tsx:138 -#: src/view/screens/Search/Search.tsx:735 +#: src/view/com/util/post-ctrls/RepostButton.tsx:139 +#: src/view/screens/Search/Search.tsx:704 #: src/view/shell/desktop/Search.tsx:218 msgid "Cancel" msgstr "취소" @@ -711,7 +739,7 @@ msgstr "이미지 자르기 취소" msgid "Cancel profile editing" msgstr "프로필 편집 취소" -#: src/view/com/util/post-ctrls/RepostButton.tsx:132 +#: src/view/com/util/post-ctrls/RepostButton.tsx:133 msgid "Cancel quote post" msgstr "게시물 인용 취소" @@ -807,7 +835,7 @@ msgstr "이메일에서 로그인 코드를 확인한 후 여기에 입력하세 msgid "Check your inbox for an email with the confirmation code to enter below:" msgstr "받은 편지함에서 아래에 입력하는 인증 코드가 포함된 이메일이 있는지 확인하세요:" -#: src/view/com/modals/Threadgate.tsx:73 +#: src/view/com/modals/Threadgate.tsx:75 msgid "Choose \"Everybody\" or \"Nobody\"" msgstr "\"모두\" 또는 \"없음\"을 선택하세요." @@ -844,7 +872,7 @@ msgid "Clear all storage data (restart after this)" msgstr "모든 스토리지 데이터 지우기 (이후 다시 시작)" #: src/view/com/util/forms/SearchInput.tsx:88 -#: src/view/screens/Search/Search.tsx:861 +#: src/view/screens/Search/Search.tsx:824 msgid "Clear search query" msgstr "검색어 지우기" @@ -889,7 +917,7 @@ msgstr "다그닥 🐴 다그닥 🐴" #: src/components/dms/dialogs/SearchablePeopleList.tsx:261 #: src/view/com/modals/ChangePassword.tsx:268 #: src/view/com/modals/ChangePassword.tsx:271 -#: src/view/com/util/post-embeds/GifEmbed.tsx:185 +#: src/view/com/util/post-embeds/GifEmbed.tsx:189 msgid "Close" msgstr "닫기" @@ -944,7 +972,7 @@ msgstr "하단 탐색 막대를 닫습니다" msgid "Closes password update alert" msgstr "비밀번호 변경 알림을 닫습니다" -#: src/view/com/composer/Composer.tsx:434 +#: src/view/com/composer/Composer.tsx:436 msgid "Closes post composer and discards post draft" msgstr "게시물 작성 상자를 닫고 게시물 초안을 삭제합니다" @@ -952,11 +980,11 @@ msgstr "게시물 작성 상자를 닫고 게시물 초안을 삭제합니다" msgid "Closes viewer for header image" msgstr "헤더 이미지 뷰어를 닫습니다" -#: src/view/com/notifications/FeedItem.tsx:205 +#: src/view/com/notifications/FeedItem.tsx:207 msgid "Collapse list of users" msgstr "사용자 목록 접기" -#: src/view/com/notifications/FeedItem.tsx:341 +#: src/view/com/notifications/FeedItem.tsx:343 msgid "Collapses list of users for a given notification" msgstr "이 알림에 대한 사용자 목록을 축소합니다" @@ -981,7 +1009,7 @@ msgstr "온보딩 완료 후 계정 사용 시작" msgid "Complete the challenge" msgstr "챌린지 완료하기" -#: src/view/com/composer/Composer.tsx:551 +#: src/view/com/composer/Composer.tsx:553 msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" msgstr "최대 {MAX_GRAPHEME_LENGTH}자 길이까지 글을 작성할 수 있습니다" @@ -997,8 +1025,8 @@ msgstr "{name} 카테고리에 대한 콘텐츠 필터링 설정을 구성합니 msgid "Configured in <0>moderation settings." msgstr "<0>검토 설정에서 설정합니다." -#: src/components/Prompt.tsx:159 #: src/components/Prompt.tsx:162 +#: src/components/Prompt.tsx:165 #: src/view/com/modals/SelfLabel.tsx:155 #: src/view/com/modals/VerifyEmail.tsx:239 #: src/view/com/modals/VerifyEmail.tsx:241 @@ -1092,7 +1120,7 @@ msgstr "{0}(으)로 계속하기 (현재 로그인)" #: src/view/com/post-thread/PostThreadLoadMore.tsx:52 msgid "Continue thread..." -msgstr "" +msgstr "스레드 더 보기..." #: src/screens/Onboarding/StepInterests/index.tsx:250 #: src/screens/Onboarding/StepProfile/index.tsx:266 @@ -1121,7 +1149,7 @@ msgstr "빌드 버전 클립보드에 복사됨" #: src/view/com/modals/AddAppPasswords.tsx:80 #: src/view/com/modals/ChangeHandle.tsx:320 #: src/view/com/modals/InviteCodes.tsx:153 -#: src/view/com/util/forms/PostDropdownBtn.tsx:182 +#: src/view/com/util/forms/PostDropdownBtn.tsx:189 msgid "Copied to clipboard" msgstr "클립보드에 복사됨" @@ -1150,8 +1178,8 @@ msgstr "코드 복사" msgid "Copy link to list" msgstr "리스트 링크 복사" -#: src/view/com/util/forms/PostDropdownBtn.tsx:297 -#: src/view/com/util/forms/PostDropdownBtn.tsx:306 +#: src/view/com/util/forms/PostDropdownBtn.tsx:307 +#: src/view/com/util/forms/PostDropdownBtn.tsx:316 msgid "Copy link to post" msgstr "게시물 링크 복사" @@ -1160,8 +1188,8 @@ msgstr "게시물 링크 복사" msgid "Copy message text" msgstr "메시지 텍스트 복사" -#: src/view/com/util/forms/PostDropdownBtn.tsx:275 -#: src/view/com/util/forms/PostDropdownBtn.tsx:277 +#: src/view/com/util/forms/PostDropdownBtn.tsx:285 +#: src/view/com/util/forms/PostDropdownBtn.tsx:287 msgid "Copy post text" msgstr "게시물 텍스트 복사" @@ -1238,7 +1266,8 @@ msgstr "사용자 지정" msgid "Custom domain" msgstr "사용자 지정 도메인" -#: src/view/screens/Feeds.tsx:797 +#: src/view/screens/Feeds.tsx:763 +#: src/view/screens/Search/Explore.tsx:383 msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." msgstr "커뮤니티에서 구축한 맞춤 피드는 새로운 경험을 제공하고 좋아하는 콘텐츠를 찾을 수 있도록 도와줍니다." @@ -1281,7 +1310,7 @@ msgid "Debug panel" msgstr "디버그 패널" #: src/components/dms/MessageMenu.tsx:151 -#: src/view/com/util/forms/PostDropdownBtn.tsx:423 +#: src/view/com/util/forms/PostDropdownBtn.tsx:433 #: src/view/screens/AppPasswords.tsx:285 #: src/view/screens/ProfileList.tsx:667 msgid "Delete" @@ -1332,8 +1361,8 @@ msgstr "내 계정 삭제" msgid "Delete My Account…" msgstr "내 계정 삭제…" -#: src/view/com/util/forms/PostDropdownBtn.tsx:404 -#: src/view/com/util/forms/PostDropdownBtn.tsx:406 +#: src/view/com/util/forms/PostDropdownBtn.tsx:414 +#: src/view/com/util/forms/PostDropdownBtn.tsx:416 msgid "Delete post" msgstr "게시물 삭제" @@ -1341,7 +1370,7 @@ msgstr "게시물 삭제" msgid "Delete this list?" msgstr "이 리스트를 삭제하시겠습니까?" -#: src/view/com/util/forms/PostDropdownBtn.tsx:418 +#: src/view/com/util/forms/PostDropdownBtn.tsx:428 msgid "Delete this post?" msgstr "이 게시물을 삭제하시겠습니까?" @@ -1349,7 +1378,7 @@ msgstr "이 게시물을 삭제하시겠습니까?" msgid "Deleted" msgstr "삭제됨" -#: src/view/com/post-thread/PostThread.tsx:349 +#: src/view/com/post-thread/PostThread.tsx:353 msgid "Deleted post." msgstr "삭제된 게시물." @@ -1380,7 +1409,7 @@ msgstr "어둑함" msgid "Direct messages are here!" msgstr "다이렉트 메시지가 생겼습니다!" -#: src/view/screens/AccessibilitySettings.tsx:94 +#: src/view/screens/AccessibilitySettings.tsx:107 msgid "Disable autoplay for GIFs" msgstr "GIF 자동 재생 끄기" @@ -1388,7 +1417,7 @@ msgstr "GIF 자동 재생 끄기" msgid "Disable Email 2FA" msgstr "이메일 2단계 인증 끄기" -#: src/view/screens/AccessibilitySettings.tsx:108 +#: src/view/screens/AccessibilitySettings.tsx:121 msgid "Disable haptic feedback" msgstr "햅틱 피드백 끄기" @@ -1401,11 +1430,11 @@ msgstr "햅틱 피드백 끄기" msgid "Disabled" msgstr "사용 안 함" -#: src/view/com/composer/Composer.tsx:632 +#: src/view/com/composer/Composer.tsx:634 msgid "Discard" msgstr "삭제" -#: src/view/com/composer/Composer.tsx:629 +#: src/view/com/composer/Composer.tsx:631 msgid "Discard draft?" msgstr "초안 삭제" @@ -1419,10 +1448,18 @@ msgstr "앱이 로그아웃한 사용자에게 내 계정을 표시하지 않도 msgid "Discover new custom feeds" msgstr "새로운 맞춤 피드 찾아보기" -#: src/view/screens/Feeds.tsx:794 +#: src/view/screens/Search/Explore.tsx:381 +msgid "Discover new feeds" +msgstr "새 피드 발견하기" + +#: src/view/screens/Feeds.tsx:760 msgid "Discover New Feeds" msgstr "새 피드 발견하기" +#: src/view/screens/AccessibilitySettings.tsx:95 +msgid "Display larger alt text badges" +msgstr "더 큰 대체 텍스트 배지 표시" + #: src/view/com/modals/EditProfile.tsx:193 msgid "Display name" msgstr "표시 이름" @@ -1453,8 +1490,8 @@ msgstr "도메인을 확인했습니다." #: src/components/dialogs/BirthDateSettings.tsx:119 #: src/components/dialogs/BirthDateSettings.tsx:125 -#: src/components/forms/DateField/index.tsx:74 -#: src/components/forms/DateField/index.tsx:80 +#: src/components/forms/DateField/index.tsx:77 +#: src/components/forms/DateField/index.tsx:83 #: src/screens/Onboarding/StepProfile/index.tsx:322 #: src/screens/Onboarding/StepProfile/index.tsx:325 #: src/view/com/auth/server-input/index.tsx:169 @@ -1472,8 +1509,8 @@ msgstr "완료" #: src/view/com/modals/EditImage.tsx:334 #: src/view/com/modals/ListAddRemoveUsers.tsx:144 #: src/view/com/modals/SelfLabel.tsx:158 -#: src/view/com/modals/Threadgate.tsx:130 #: src/view/com/modals/Threadgate.tsx:133 +#: src/view/com/modals/Threadgate.tsx:136 #: src/view/com/modals/UserAddRemoveLists.tsx:108 #: src/view/com/modals/UserAddRemoveLists.tsx:111 #: src/view/screens/PreferencesThreads.tsx:162 @@ -1490,7 +1527,7 @@ msgstr "완료{extraText}" msgid "Download CAR file" msgstr "CAR 파일 다운로드" -#: src/view/com/composer/text-input/TextInput.web.tsx:261 +#: src/view/com/composer/text-input/TextInput.web.tsx:272 msgid "Drop to add images" msgstr "드롭하여 이미지 추가" @@ -1534,17 +1571,17 @@ msgstr "예: 반복적으로 광고 답글을 다는 계정." msgid "Each code works once. You'll receive more invite codes periodically." msgstr "각 코드는 한 번만 사용할 수 있습니다. 주기적으로 더 많은 초대 코드를 받게 됩니다." -#: src/view/screens/Feeds.tsx:400 -#: src/view/screens/Feeds.tsx:471 -msgid "Edit" -msgstr "" - #: src/view/com/lists/ListMembers.tsx:149 msgctxt "action" msgid "Edit" msgstr "편집" -#: src/view/com/util/UserAvatar.tsx:312 +#: src/view/screens/Feeds.tsx:370 +#: src/view/screens/Feeds.tsx:441 +msgid "Edit" +msgstr "편집" + +#: src/view/com/util/UserAvatar.tsx:325 #: src/view/com/util/UserBanner.tsx:92 msgid "Edit avatar" msgstr "아바타 편집" @@ -1563,8 +1600,8 @@ msgid "Edit Moderation List" msgstr "검토 리스트 편집" #: src/Navigation.tsx:269 -#: src/view/screens/Feeds.tsx:398 -#: src/view/screens/Feeds.tsx:469 +#: src/view/screens/Feeds.tsx:368 +#: src/view/screens/Feeds.tsx:439 #: src/view/screens/SavedFeeds.tsx:93 msgid "Edit My Feeds" msgstr "내 피드 편집" @@ -1574,24 +1611,24 @@ msgid "Edit my profile" msgstr "내 프로필 편집" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:181 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:175 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:179 msgid "Edit profile" msgstr "프로필 편집" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:184 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:178 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:182 msgid "Edit Profile" msgstr "프로필 편집" -#: src/view/com/home/HomeHeaderLayout.web.tsx:76 -#: src/view/screens/Feeds.tsx:416 -#~ msgid "Edit Saved Feeds" -#~ msgstr "저장한 피드 편집" - #: src/view/com/modals/CreateOrEditList.tsx:234 msgid "Edit User List" msgstr "사용자 리스트 편집" +#: src/view/com/threadgate/WhoCanReply.tsx:73 +#: src/view/com/threadgate/WhoCanReply.tsx:130 +msgid "Edit who can reply" +msgstr "답글을 달 수 있는 사람 편집" + #: src/view/com/modals/EditProfile.tsx:194 msgid "Edit your display name" msgstr "내 표시 이름 편집" @@ -1639,8 +1676,8 @@ msgid "Embed HTML code" msgstr "임베드 HTML 코드" #: src/components/dialogs/Embed.tsx:97 -#: src/view/com/util/forms/PostDropdownBtn.tsx:314 -#: src/view/com/util/forms/PostDropdownBtn.tsx:316 +#: src/view/com/util/forms/PostDropdownBtn.tsx:324 +#: src/view/com/util/forms/PostDropdownBtn.tsx:326 msgid "Embed post" msgstr "게시물 임베드" @@ -1746,11 +1783,14 @@ msgstr "캡차 응답을 수신하는 동안 오류가 발생했습니다." msgid "Error:" msgstr "오류:" -#: src/view/com/modals/Threadgate.tsx:77 +#: src/view/com/modals/Threadgate.tsx:79 msgid "Everybody" msgstr "모두" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:46 +#: src/view/com/threadgate/WhoCanReply.tsx:64 +#: src/view/com/threadgate/WhoCanReply.tsx:121 +#: src/view/com/threadgate/WhoCanReply.tsx:235 msgid "Everybody can reply" msgstr "누구나 답글을 달 수 있음" @@ -1794,7 +1834,7 @@ msgstr "검색어 입력을 종료합니다" msgid "Expand alt text" msgstr "대체 텍스트 확장" -#: src/view/com/notifications/FeedItem.tsx:206 +#: src/view/com/notifications/FeedItem.tsx:208 msgid "Expand list of users" msgstr "사용자 목록 펼치기" @@ -1853,10 +1893,15 @@ msgstr "리스트를 만들지 못했습니다. 인터넷 연결을 확인한 msgid "Failed to delete message" msgstr "메시지를 삭제하지 못했습니다" -#: src/view/com/util/forms/PostDropdownBtn.tsx:149 +#: src/view/com/util/forms/PostDropdownBtn.tsx:152 msgid "Failed to delete post, please try again" msgstr "게시물을 삭제하지 못했습니다. 다시 시도해 주세요" +#: src/view/screens/Search/Explore.tsx:417 +#: src/view/screens/Search/Explore.tsx:441 +msgid "Failed to load feeds preferences" +msgstr "피드 환경설정을 불러오지 못했습니다" + #: src/components/dialogs/GifSelect.ios.tsx:196 #: src/components/dialogs/GifSelect.tsx:212 msgid "Failed to load GIFs" @@ -1866,6 +1911,15 @@ msgstr "GIF를 불러오지 못했습니다" msgid "Failed to load past messages" msgstr "지난 메시지를 불러오지 못했습니다" +#: src/view/screens/Search/Explore.tsx:410 +#: src/view/screens/Search/Explore.tsx:434 +msgid "Failed to load suggested feeds" +msgstr "추천 피드를 불러오지 못했습니다" + +#: src/view/screens/Search/Explore.tsx:370 +msgid "Failed to load suggested follows" +msgstr "추천 팔로우를 불러오지 못했습니다" + #: src/view/com/lightbox/Lightbox.tsx:84 msgid "Failed to save image: {0}" msgstr "이미지를 저장하지 못함: {0}" @@ -1877,7 +1931,15 @@ msgstr "전송 실패" #: src/components/moderation/LabelsOnMeDialog.tsx:223 #: src/screens/Messages/Conversation/ChatDisabled.tsx:87 msgid "Failed to submit appeal, please try again." -msgstr "이의신청을 제출하지 못했습니다. 다시 시도하세요." +msgstr "이의신청을 제출하지 못했습니다. 다시 시도해 주세요." + +#: src/view/com/util/forms/PostDropdownBtn.tsx:180 +msgid "Failed to toggle thread mute, please try again" +msgstr "스레드 뮤트를 전환하지 못했습니다. 다시 시도해 주세요" + +#: src/components/FeedCard.tsx:160 +msgid "Failed to update feeds" +msgstr "피드를 업데이트하지 못했습니다" #: src/components/dms/MessagesNUX.tsx:60 #: src/screens/Messages/Settings.tsx:35 @@ -1888,11 +1950,12 @@ msgstr "설정을 업데이트하지 못했습니다" msgid "Feed" msgstr "피드" +#: src/components/FeedCard.tsx:91 #: src/view/com/feeds/FeedSourceCard.tsx:251 msgid "Feed by {0}" msgstr "{0} 님의 피드" -#: src/view/screens/Feeds.tsx:709 +#: src/view/screens/Feeds.tsx:675 msgid "Feed offline" msgstr "피드 오프라인" @@ -1901,9 +1964,10 @@ msgstr "피드 오프라인" msgid "Feedback" msgstr "피드백" -#: src/view/screens/Feeds.tsx:463 -#: src/view/screens/Feeds.tsx:570 +#: src/view/screens/Feeds.tsx:433 +#: src/view/screens/Feeds.tsx:536 #: src/view/screens/Profile.tsx:197 +#: src/view/screens/Search/Search.tsx:375 #: src/view/shell/desktop/LeftNav.tsx:367 #: src/view/shell/Drawer.tsx:493 #: src/view/shell/Drawer.tsx:494 @@ -1914,6 +1978,10 @@ msgstr "피드" msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." msgstr "피드는 사용자가 약간의 코딩 전문 지식만으로 구축할 수 있는 맞춤 알고리즘입니다. <0/>에서 자세한 내용을 확인하세요." +#: src/components/FeedCard.tsx:157 +msgid "Feeds updated!" +msgstr "피드 업데이트됨" + #: src/view/com/modals/ChangeHandle.tsx:475 msgid "File Contents" msgstr "파일 콘텐츠" @@ -1936,7 +2004,7 @@ msgstr "마무리 중" msgid "Find accounts to follow" msgstr "팔로우할 계정 찾아보기" -#: src/view/screens/Search/Search.tsx:470 +#: src/view/screens/Search/Search.tsx:439 msgid "Find posts and users on Bluesky" msgstr "Bluesky에서 게시물 및 사용자 찾기" @@ -1965,9 +2033,9 @@ msgstr "가로로 뒤집기" msgid "Flip vertically" msgstr "세로로 뒤집기" -#: src/components/ProfileHoverCard/index.web.tsx:412 -#: src/components/ProfileHoverCard/index.web.tsx:423 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:248 +#: src/components/ProfileHoverCard/index.web.tsx:446 +#: src/components/ProfileHoverCard/index.web.tsx:457 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:252 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:146 #: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:247 msgid "Follow" @@ -1978,7 +2046,7 @@ msgctxt "action" msgid "Follow" msgstr "팔로우" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:234 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:238 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:128 msgid "Follow {0}" msgstr "{0} 님을 팔로우" @@ -1987,8 +2055,8 @@ msgstr "{0} 님을 팔로우" msgid "Follow {name}" msgstr "{name} 님을 팔로우" -#: src/view/com/profile/ProfileMenu.tsx:244 -#: src/view/com/profile/ProfileMenu.tsx:255 +#: src/view/com/profile/ProfileMenu.tsx:247 +#: src/view/com/profile/ProfileMenu.tsx:258 msgid "Follow Account" msgstr "계정 팔로우" @@ -1996,15 +2064,31 @@ msgstr "계정 팔로우" msgid "Follow Back" msgstr "맞팔로우" -#: src/components/KnownFollowers.tsx:169 -msgid "Followed by" -msgstr "" +#: src/view/screens/Search/Explore.tsx:333 +msgid "Follow more accounts to get connected to your interests and build your network." +msgstr "더 많은 계정을 팔로우하고 관심 분야를 연결하여 네트워크를 구축하세요." #: src/view/com/profile/ProfileCard.tsx:227 msgid "Followed by {0}" msgstr "{0} 님이 팔로우함" -#: src/view/com/modals/Threadgate.tsx:99 +#: src/components/KnownFollowers.tsx:223 +msgid "Followed by <0>{0}" +msgstr "<0>{0} 님이 팔로우함" + +#: src/components/KnownFollowers.tsx:209 +msgid "Followed by <0>{0} and {1, plural, one {# other} other {# others}}" +msgstr "<0>{0} 님 외 {1, plural, other {#}}명이 팔로우함" + +#: src/components/KnownFollowers.tsx:196 +msgid "Followed by <0>{0} and <1>{1}" +msgstr "<0>{0} 님과 <1>{1} 님이 팔로우함" + +#: src/components/KnownFollowers.tsx:178 +msgid "Followed by <0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}}" +msgstr "<0>{0} 님, <1>{1} 님 외 {2, plural, other {#}}명이 팔로우함" + +#: src/view/com/modals/Threadgate.tsx:101 msgid "Followed users" msgstr "팔로우한 사용자" @@ -2012,7 +2096,7 @@ msgstr "팔로우한 사용자" msgid "Followed users only" msgstr "팔로우한 사용자만" -#: src/view/com/notifications/FeedItem.tsx:173 +#: src/view/com/notifications/FeedItem.tsx:175 msgid "followed you" msgstr "이(가) 나를 팔로우했습니다" @@ -2023,25 +2107,25 @@ msgstr "팔로워" #: src/Navigation.tsx:177 msgid "Followers of @{0} that you know" -msgstr "" +msgstr "내가 아는 @{0} 님의 팔로워" #: src/screens/Profile/KnownFollowers.tsx:108 #: src/screens/Profile/KnownFollowers.tsx:118 msgid "Followers you know" -msgstr "" +msgstr "내가 아는 팔로워" -#: src/components/ProfileHoverCard/index.web.tsx:411 -#: src/components/ProfileHoverCard/index.web.tsx:422 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:246 +#: src/components/ProfileHoverCard/index.web.tsx:445 +#: src/components/ProfileHoverCard/index.web.tsx:456 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:250 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:149 #: src/view/com/profile/ProfileFollows.tsx:104 -#: src/view/screens/Feeds.tsx:656 +#: src/view/screens/Feeds.tsx:622 #: src/view/screens/ProfileFollows.tsx:25 #: src/view/screens/SavedFeeds.tsx:415 msgid "Following" msgstr "팔로우 중" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:94 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:98 msgid "Following {0}" msgstr "{0} 님을 팔로우했습니다" @@ -2059,7 +2143,7 @@ msgstr "팔로우 중 피드 설정" msgid "Following Feed Preferences" msgstr "팔로우 중 피드 설정" -#: src/screens/Profile/Header/Handle.tsx:24 +#: src/screens/Profile/Header/Handle.tsx:31 msgid "Follows you" msgstr "나를 팔로우함" @@ -2100,7 +2184,7 @@ msgstr "잦은 원치 않는 콘텐츠 게시" msgid "From @{sanitizedAuthor}" msgstr "@{sanitizedAuthor} 님의 태그" -#: src/view/com/posts/FeedItem.tsx:232 +#: src/view/com/posts/FeedItem.tsx:236 msgctxt "from-feed" msgid "From <0/>" msgstr "<0/>에서" @@ -2118,6 +2202,10 @@ msgstr "시작하기" msgid "Get Started" msgstr "시작하기" +#: src/view/com/util/images/ImageHorzList.tsx:35 +msgid "GIF" +msgstr "GIF" + #: src/screens/Onboarding/StepProfile/index.tsx:225 msgid "Give your profile a face" msgstr "프로필에 얼굴 달기" @@ -2188,7 +2276,7 @@ msgstr "그래픽 미디어" msgid "Handle" msgstr "핸들" -#: src/view/screens/AccessibilitySettings.tsx:103 +#: src/view/screens/AccessibilitySettings.tsx:116 msgid "Haptics" msgstr "햅틱" @@ -2223,35 +2311,35 @@ msgstr "앱 비밀번호입니다." #: src/components/moderation/ContentHider.tsx:116 #: src/components/moderation/LabelPreference.tsx:134 -#: src/components/moderation/PostHider.tsx:121 +#: src/components/moderation/PostHider.tsx:122 #: src/lib/moderation/useLabelBehaviorDescription.ts:15 #: src/lib/moderation/useLabelBehaviorDescription.ts:20 #: src/lib/moderation/useLabelBehaviorDescription.ts:25 #: src/lib/moderation/useLabelBehaviorDescription.ts:30 -#: src/view/com/util/forms/PostDropdownBtn.tsx:432 +#: src/view/com/util/forms/PostDropdownBtn.tsx:442 msgid "Hide" msgstr "숨기기" -#: src/view/com/notifications/FeedItem.tsx:348 +#: src/view/com/notifications/FeedItem.tsx:350 msgctxt "action" msgid "Hide" msgstr "숨기기" -#: src/view/com/util/forms/PostDropdownBtn.tsx:377 -#: src/view/com/util/forms/PostDropdownBtn.tsx:379 +#: src/view/com/util/forms/PostDropdownBtn.tsx:387 +#: src/view/com/util/forms/PostDropdownBtn.tsx:389 msgid "Hide post" msgstr "게시물 숨기기" #: src/components/moderation/ContentHider.tsx:68 -#: src/components/moderation/PostHider.tsx:78 +#: src/components/moderation/PostHider.tsx:79 msgid "Hide the content" msgstr "콘텐츠 숨기기" -#: src/view/com/util/forms/PostDropdownBtn.tsx:429 +#: src/view/com/util/forms/PostDropdownBtn.tsx:439 msgid "Hide this post?" msgstr "이 게시물을 숨기시겠습니까?" -#: src/view/com/notifications/FeedItem.tsx:339 +#: src/view/com/notifications/FeedItem.tsx:341 msgid "Hide user list" msgstr "사용자 리스트 숨기기" @@ -2341,7 +2429,7 @@ msgstr "해당 국가의 법률에 따라 아직 성인이 아닌 경우, 부모 msgid "If you delete this list, you won't be able to recover it." msgstr "이 리스트를 삭제하면 다시 복구할 수 없습니다." -#: src/view/com/util/forms/PostDropdownBtn.tsx:420 +#: src/view/com/util/forms/PostDropdownBtn.tsx:430 msgid "If you remove this post, you won't be able to recover it." msgstr "이 게시물을 삭제하면 다시 복구할 수 없습니다." @@ -2357,7 +2445,7 @@ msgstr "핸들이나 이메일을 변경하려는 경우 비활성화하기 전 msgid "Illegal and Urgent" msgstr "불법 및 긴급 사항" -#: src/view/com/util/images/Gallery.tsx:39 +#: src/view/com/util/images/Gallery.tsx:42 msgid "Image" msgstr "이미지" @@ -2426,7 +2514,7 @@ msgstr "다이렉트 메시지 소개" msgid "Invalid 2FA confirmation code." msgstr "잘못된 2단계 인증 코드입니다." -#: src/view/com/post-thread/PostThreadItem.tsx:235 +#: src/view/com/post-thread/PostThreadItem.tsx:236 msgid "Invalid or unsupported post record" msgstr "유효하지 않거나 지원되지 않는 게시물 기록" @@ -2442,7 +2530,7 @@ msgstr "친구 초대하기" msgid "Invite code" msgstr "초대 코드" -#: src/screens/Signup/state.ts:272 +#: src/screens/Signup/state.ts:275 msgid "Invite code not accepted. Check that you input it correctly and try again." msgstr "초대 코드가 올바르지 않습니다. 코드를 올바르게 입력했는지 확인한 후 다시 시도하세요." @@ -2504,7 +2592,7 @@ msgid "Languages" msgstr "언어" #: src/screens/Hashtag.tsx:99 -#: src/view/screens/Search/Search.tsx:377 +#: src/view/screens/Search/Search.tsx:359 msgid "Latest" msgstr "최신" @@ -2517,7 +2605,7 @@ msgstr "더 알아보기" msgid "Learn more about the moderation applied to this content." msgstr "이 콘텐츠에 적용된 검토 설정에 대해 자세히 알아보세요." -#: src/components/moderation/PostHider.tsx:99 +#: src/components/moderation/PostHider.tsx:100 #: src/components/moderation/ScreenHider.tsx:125 msgid "Learn more about this warning" msgstr "이 경고에 대해 더 알아보기" @@ -2593,11 +2681,11 @@ msgstr "좋아요 표시한 사용자" msgid "Liked By" msgstr "좋아요 표시한 사용자" -#: src/view/com/notifications/FeedItem.tsx:176 +#: src/view/com/notifications/FeedItem.tsx:178 msgid "liked your custom feed" msgstr "이(가) 내 맞춤 피드를 좋아합니다" -#: src/view/com/notifications/FeedItem.tsx:168 +#: src/view/com/notifications/FeedItem.tsx:170 msgid "liked your post" msgstr "이(가) 내 게시물을 좋아합니다" @@ -2605,7 +2693,7 @@ msgstr "이(가) 내 게시물을 좋아합니다" msgid "Likes" msgstr "좋아요" -#: src/view/com/post-thread/PostThreadItem.tsx:196 +#: src/view/com/post-thread/PostThreadItem.tsx:197 msgid "Likes on this post" msgstr "이 게시물을 좋아요 표시합니다" @@ -2658,6 +2746,18 @@ msgstr "리스트" msgid "Lists blocking this user:" msgstr "이 사용자를 차단한 리스트:" +#: src/view/screens/Search/Explore.tsx:130 +msgid "Load more" +msgstr "더 불러오기" + +#: src/view/screens/Search/Explore.tsx:218 +msgid "Load more suggested feeds" +msgstr "추천 피드 더 불러오기" + +#: src/view/screens/Search/Explore.tsx:216 +msgid "Load more suggested follows" +msgstr "추천 팔로우 더 불러오기" + #: src/view/screens/Notifications.tsx:184 msgid "Load new notifications" msgstr "새 알림 불러오기" @@ -2730,21 +2830,21 @@ msgstr "뮤트한 단어 및 태그 관리" msgid "Mark as read" msgstr "읽음으로 표시" -#: src/view/screens/AccessibilitySettings.tsx:89 +#: src/view/screens/AccessibilitySettings.tsx:102 #: src/view/screens/Profile.tsx:195 msgid "Media" msgstr "미디어" -#: src/view/com/threadgate/WhoCanReply.tsx:139 +#: src/view/com/threadgate/WhoCanReply.tsx:270 msgid "mentioned users" msgstr "멘션한 사용자" -#: src/view/com/modals/Threadgate.tsx:94 +#: src/view/com/modals/Threadgate.tsx:96 msgid "Mentioned users" msgstr "멘션한 사용자" #: src/view/com/util/ViewHeader.tsx:91 -#: src/view/screens/Search/Search.tsx:714 +#: src/view/screens/Search/Search.tsx:683 msgid "Menu" msgstr "메뉴" @@ -2844,7 +2944,7 @@ msgstr "검토 도구" msgid "Moderator has chosen to set a general warning on the content." msgstr "검토자가 콘텐츠에 일반 경고를 설정했습니다." -#: src/view/com/post-thread/PostThreadItem.tsx:566 +#: src/view/com/post-thread/PostThreadItem.tsx:567 msgid "More" msgstr "더 보기" @@ -2868,8 +2968,8 @@ msgstr "뮤트" msgid "Mute {truncatedTag}" msgstr "{truncatedTag} 뮤트" -#: src/view/com/profile/ProfileMenu.tsx:281 -#: src/view/com/profile/ProfileMenu.tsx:288 +#: src/view/com/profile/ProfileMenu.tsx:284 +#: src/view/com/profile/ProfileMenu.tsx:291 msgid "Mute Account" msgstr "계정 뮤트" @@ -2910,13 +3010,13 @@ msgstr "게시물 글 및 태그에서 이 단어 뮤트하기" msgid "Mute this word in tags only" msgstr "태그에서만 이 단어 뮤트하기" -#: src/view/com/util/forms/PostDropdownBtn.tsx:352 -#: src/view/com/util/forms/PostDropdownBtn.tsx:358 +#: src/view/com/util/forms/PostDropdownBtn.tsx:362 +#: src/view/com/util/forms/PostDropdownBtn.tsx:368 msgid "Mute thread" msgstr "스레드 뮤트" -#: src/view/com/util/forms/PostDropdownBtn.tsx:368 -#: src/view/com/util/forms/PostDropdownBtn.tsx:370 +#: src/view/com/util/forms/PostDropdownBtn.tsx:378 +#: src/view/com/util/forms/PostDropdownBtn.tsx:380 msgid "Mute words & tags" msgstr "단어 및 태그 뮤트" @@ -2954,7 +3054,7 @@ msgstr "뮤트 목록은 비공개입니다. 뮤트한 계정은 나와 상호 msgid "My Birthday" msgstr "내 생년월일" -#: src/view/screens/Feeds.tsx:768 +#: src/view/screens/Feeds.tsx:734 msgid "My Feeds" msgstr "내 피드" @@ -3047,7 +3147,7 @@ msgctxt "action" msgid "New post" msgstr "새 게시물" -#: src/view/screens/Feeds.tsx:600 +#: src/view/screens/Feeds.tsx:566 #: src/view/screens/Notifications.tsx:193 #: src/view/screens/Profile.tsx:464 #: src/view/screens/ProfileFeed.tsx:426 @@ -3062,6 +3162,10 @@ msgctxt "action" msgid "New Post" msgstr "새 게시물" +#: src/components/NewskieDialog.tsx:68 +msgid "New user info dialog" +msgstr "새 사용자 정보 대화 상자" + #: src/view/com/modals/CreateOrEditList.tsx:236 msgid "New User List" msgstr "새 사용자 리스트" @@ -3113,7 +3217,7 @@ msgstr "DNS 패널 없음" msgid "No featured GIFs found. There may be an issue with Tenor." msgstr "인기 GIF를 찾을 수 없습니다. Tenor에 문제가 있을 수 있습니다." -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:116 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:120 msgid "No longer following {0}" msgstr "더 이상 {0} 님을 팔로우하지 않음" @@ -3157,13 +3261,14 @@ msgstr "결과 없음" msgid "No results found" msgstr "결과를 찾을 수 없음" -#: src/view/screens/Feeds.tsx:530 +#: src/view/screens/Feeds.tsx:497 msgid "No results found for \"{query}\"" msgstr "\"{query}\"에 대한 결과를 찾을 수 없습니다" #: src/view/com/modals/ListAddRemoveUsers.tsx:127 -#: src/view/screens/Search/Search.tsx:297 -#: src/view/screens/Search/Search.tsx:336 +#: src/view/screens/Search/Search.tsx:233 +#: src/view/screens/Search/Search.tsx:272 +#: src/view/screens/Search/Search.tsx:318 msgid "No results found for {query}" msgstr "{query}에 대한 결과를 찾을 수 없습니다" @@ -3177,7 +3282,7 @@ msgstr "\"{search}\"에 대한 검색 결과를 찾을 수 없습니다." msgid "No thanks" msgstr "사용하지 않음" -#: src/view/com/modals/Threadgate.tsx:83 +#: src/view/com/modals/Threadgate.tsx:85 msgid "Nobody" msgstr "없음" @@ -3204,9 +3309,9 @@ msgstr "찾을 수 없음" msgid "Not right now" msgstr "나중에 하기" -#: src/view/com/profile/ProfileMenu.tsx:370 -#: src/view/com/util/forms/PostDropdownBtn.tsx:446 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:308 +#: src/view/com/profile/ProfileMenu.tsx:373 +#: src/view/com/util/forms/PostDropdownBtn.tsx:456 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:311 msgid "Note about sharing" msgstr "공유 관련 참고 사항" @@ -3236,6 +3341,10 @@ msgstr "알림음" msgid "Notifications" msgstr "알림" +#: src/lib/hooks/useTimeAgo.ts:51 +msgid "now" +msgstr "지금" + #: src/components/dms/MessageItem.tsx:175 msgid "Now" msgstr "지금" @@ -3274,11 +3383,15 @@ msgstr "확인" msgid "Oldest replies first" msgstr "오래된 순" +#: src/lib/hooks/useTimeAgo.ts:81 +msgid "on {str}" +msgstr "" + #: src/view/screens/Settings/index.tsx:256 msgid "Onboarding reset" msgstr "온보딩 재설정" -#: src/view/com/composer/Composer.tsx:503 +#: src/view/com/composer/Composer.tsx:505 msgid "One or more images is missing alt text." msgstr "하나 이상의 이미지에 대체 텍스트가 누락되었습니다." @@ -3286,9 +3399,9 @@ msgstr "하나 이상의 이미지에 대체 텍스트가 누락되었습니다. msgid "Only .jpg and .png files are supported" msgstr ".jpg 및 .png 파일만 지원합니다" -#: src/view/com/threadgate/WhoCanReply.tsx:100 -msgid "Only {0} can reply." -msgstr "{0}만 답글을 달 수 있습니다." +#: src/view/com/threadgate/WhoCanReply.tsx:239 +msgid "Only {0} can reply" +msgstr "{0}만 답글을 달 수 있음" #: src/screens/Signup/StepHandle.tsx:98 msgid "Only contains letters, numbers, and hyphens" @@ -3321,8 +3434,8 @@ msgstr "아바타 생성기 열기" msgid "Open conversation options" msgstr "대화 옵션 열기" -#: src/view/com/composer/Composer.tsx:613 -#: src/view/com/composer/Composer.tsx:614 +#: src/view/com/composer/Composer.tsx:615 +#: src/view/com/composer/Composer.tsx:616 msgid "Open emoji picker" msgstr "이모티콘 선택기 열기" @@ -3346,7 +3459,7 @@ msgstr "뮤트한 단어 및 태그 설정 열기" msgid "Open navigation" msgstr "내비게이션 열기" -#: src/view/com/util/forms/PostDropdownBtn.tsx:237 +#: src/view/com/util/forms/PostDropdownBtn.tsx:247 msgid "Open post options menu" msgstr "게시물 옵션 메뉴 열기" @@ -3367,7 +3480,7 @@ msgstr "{numItems}번째 옵션을 엽니다" msgid "Opens accessibility settings" msgstr "접근성 설정을 엽니다" -#: src/view/screens/Log.tsx:54 +#: src/view/screens/Log.tsx:58 msgid "Opens additional details for a debug entry" msgstr "디버그 항목에 대한 추가 세부 정보를 엽니다" @@ -3449,11 +3562,6 @@ msgstr "검토 설정을 엽니다" msgid "Opens password reset form" msgstr "비밀번호 재설정 양식을 엽니다" -#: src/view/com/home/HomeHeaderLayout.web.tsx:77 -#: src/view/screens/Feeds.tsx:417 -#~ msgid "Opens screen to edit Saved Feeds" -#~ msgstr "저장한 피드를 편집할 수 있는 화면을 엽니다" - #: src/view/screens/Settings/index.tsx:617 msgid "Opens screen with all saved feeds" msgstr "모든 저장한 피드 화면을 엽니다" @@ -3483,8 +3591,8 @@ msgstr "시스템 로그 페이지를 엽니다" msgid "Opens the threads preferences" msgstr "스레드 설정을 엽니다" -#: src/view/com/notifications/FeedItem.tsx:427 -#: src/view/com/util/UserAvatar.tsx:409 +#: src/view/com/notifications/FeedItem.tsx:429 +#: src/view/com/util/UserAvatar.tsx:422 msgid "Opens this profile" msgstr "이 프로필을 엽니다" @@ -3497,7 +3605,7 @@ msgstr "{numItems}개 중 {0}번째 옵션" msgid "Optionally provide additional information below:" msgstr "선택 사항으로 아래에 추가 정보를 입력하세요:" -#: src/view/com/modals/Threadgate.tsx:90 +#: src/view/com/modals/Threadgate.tsx:92 msgid "Or combine these options:" msgstr "또는 다음 옵션을 결합하세요:" @@ -3553,11 +3661,11 @@ msgstr "비밀번호 변경됨" msgid "Password updated!" msgstr "비밀번호 변경됨" -#: src/view/com/util/post-embeds/GifEmbed.tsx:36 +#: src/view/com/util/post-embeds/GifEmbed.tsx:37 msgid "Pause" msgstr "일시 정지" -#: src/view/screens/Search/Search.tsx:387 +#: src/view/screens/Search/Search.tsx:369 msgid "People" msgstr "사람들" @@ -3602,7 +3710,7 @@ msgstr "고정한 피드" msgid "Pinned to your feeds" msgstr "내 피드에 고정됨" -#: src/view/com/util/post-embeds/GifEmbed.tsx:36 +#: src/view/com/util/post-embeds/GifEmbed.tsx:37 msgid "Play" msgstr "재생" @@ -3610,7 +3718,7 @@ msgstr "재생" msgid "Play {0}" msgstr "{0} 재생" -#: src/view/com/util/post-embeds/GifEmbed.tsx:35 +#: src/view/com/util/post-embeds/GifEmbed.tsx:36 msgid "Play or pause the GIF" msgstr "GIP를 재생하거나 일시 정지합니다" @@ -3688,13 +3796,13 @@ msgstr "정치" msgid "Porn" msgstr "음란물" -#: src/view/com/composer/Composer.tsx:477 -#: src/view/com/composer/Composer.tsx:485 +#: src/view/com/composer/Composer.tsx:479 +#: src/view/com/composer/Composer.tsx:487 msgctxt "action" msgid "Post" msgstr "게시하기" -#: src/view/com/post-thread/PostThread.tsx:430 +#: src/view/com/post-thread/PostThread.tsx:434 msgctxt "description" msgid "Post" msgstr "게시물" @@ -3709,7 +3817,7 @@ msgstr "{0} 님의 게시물" msgid "Post by @{0}" msgstr "@{0} 님의 게시물" -#: src/view/com/util/forms/PostDropdownBtn.tsx:129 +#: src/view/com/util/forms/PostDropdownBtn.tsx:132 msgid "Post deleted" msgstr "게시물 삭제됨" @@ -3775,9 +3883,9 @@ msgstr "호스팅 제공자를 변경하려면 누릅니다" msgid "Press to retry" msgstr "다시 시도하려면 누르기" -#: src/components/KnownFollowers.tsx:111 +#: src/components/KnownFollowers.tsx:116 msgid "Press to view followers of this account that you also follow" -msgstr "" +msgstr "내가 팔로우하는 이 계정의 팔로워를 보려면 누르세요" #: src/view/com/lightbox/Lightbox.web.tsx:150 msgid "Previous image" @@ -3845,18 +3953,18 @@ msgstr "일괄 뮤트하거나 차단할 수 있는 공개적이고 공유 가 msgid "Public, shareable lists which can drive feeds." msgstr "피드를 탐색할 수 있는 공개적이고 공유 가능한 목록입니다." -#: src/view/com/composer/Composer.tsx:462 +#: src/view/com/composer/Composer.tsx:464 msgid "Publish post" msgstr "게시물 게시하기" -#: src/view/com/composer/Composer.tsx:462 +#: src/view/com/composer/Composer.tsx:464 msgid "Publish reply" msgstr "답글 게시하기" -#: src/view/com/util/post-ctrls/RepostButton.tsx:115 -#: src/view/com/util/post-ctrls/RepostButton.tsx:127 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:78 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:81 +#: src/view/com/util/post-ctrls/RepostButton.tsx:116 +#: src/view/com/util/post-ctrls/RepostButton.tsx:128 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:79 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:82 msgid "Quote post" msgstr "게시물 인용" @@ -3876,7 +3984,7 @@ msgstr "계정 재활성화" msgid "Reason:" msgstr "이유:" -#: src/view/screens/Search/Search.tsx:970 +#: src/view/screens/Search/Search.tsx:933 msgid "Recent Searches" msgstr "최근 검색" @@ -3889,6 +3997,7 @@ msgid "Reload conversations" msgstr "대화 다시 불러오기" #: src/components/dialogs/MutedWords.tsx:286 +#: src/components/FeedCard.tsx:200 #: src/view/com/feeds/FeedSourceCard.tsx:317 #: src/view/com/modals/ListAddRemoveUsers.tsx:268 #: src/view/com/modals/SelfLabel.tsx:84 @@ -3901,7 +4010,7 @@ msgstr "제거" msgid "Remove account" msgstr "계정 제거" -#: src/view/com/util/UserAvatar.tsx:371 +#: src/view/com/util/UserAvatar.tsx:384 msgid "Remove Avatar" msgstr "아바타 제거" @@ -3931,6 +4040,7 @@ msgstr "피드를 제거하시겠습니까?" msgid "Remove from my feeds" msgstr "내 피드에서 제거" +#: src/components/FeedCard.tsx:195 #: src/view/com/feeds/FeedSourceCard.tsx:312 msgid "Remove from my feeds?" msgstr "내 피드에서 제거하시겠습니까?" @@ -3947,11 +4057,11 @@ msgstr "이미지 미리보기 제거" msgid "Remove mute word from your list" msgstr "목록에서 뮤트한 단어 제거" -#: src/view/screens/Search/Search.tsx:1011 +#: src/view/screens/Search/Search.tsx:974 msgid "Remove profile" msgstr "프로필 제거" -#: src/view/screens/Search/Search.tsx:1013 +#: src/view/screens/Search/Search.tsx:976 msgid "Remove profile from search history" msgstr "검색 기록에서 프로필을 제거합니다" @@ -3959,8 +4069,8 @@ msgstr "검색 기록에서 프로필을 제거합니다" msgid "Remove quote" msgstr "인용 제거" -#: src/view/com/util/post-ctrls/RepostButton.tsx:92 -#: src/view/com/util/post-ctrls/RepostButton.tsx:108 +#: src/view/com/util/post-ctrls/RepostButton.tsx:93 +#: src/view/com/util/post-ctrls/RepostButton.tsx:109 msgid "Remove repost" msgstr "재게시를 취소합니다" @@ -4000,11 +4110,19 @@ msgstr "Discover로 교체" msgid "Replies" msgstr "답글" -#: src/view/com/threadgate/WhoCanReply.tsx:98 -msgid "Replies to this thread are disabled" -msgstr "이 스레드에 대한 답글이 비활성화됩니다." +#: src/view/com/threadgate/WhoCanReply.tsx:66 +msgid "Replies disabled" +msgstr "답글 비활성화됨" -#: src/view/com/composer/Composer.tsx:475 +#: src/view/com/threadgate/WhoCanReply.tsx:123 +msgid "Replies on this thread are disabled" +msgstr "이 스레드에 대한 답글이 비활성화됨" + +#: src/view/com/threadgate/WhoCanReply.tsx:237 +msgid "Replies to this thread are disabled" +msgstr "이 스레드에 대한 답글이 비활성화됨" + +#: src/view/com/composer/Composer.tsx:477 msgctxt "action" msgid "Reply" msgstr "답글" @@ -4014,19 +4132,24 @@ msgid "Reply Filters" msgstr "답글 필터" #: src/view/com/post/Post.tsx:190 -#: src/view/com/posts/FeedItem.tsx:427 +#: src/view/com/posts/FeedItem.tsx:439 msgctxt "description" msgid "Reply to <0><1/>" msgstr "<0><1/> 님에게 보내는 답글" +#: src/view/com/posts/FeedItem.tsx:437 +msgctxt "description" +msgid "Reply to a blocked post" +msgstr "차단된 게시물에 보내는 답글" + #: src/components/dms/MessageMenu.tsx:132 #: src/components/dms/MessagesListBlockedFooter.tsx:77 #: src/components/dms/MessagesListBlockedFooter.tsx:84 msgid "Report" msgstr "신고" -#: src/view/com/profile/ProfileMenu.tsx:321 #: src/view/com/profile/ProfileMenu.tsx:324 +#: src/view/com/profile/ProfileMenu.tsx:327 msgid "Report Account" msgstr "계정 신고" @@ -4053,8 +4176,8 @@ msgstr "리스트 신고" msgid "Report message" msgstr "메시지 신고" -#: src/view/com/util/forms/PostDropdownBtn.tsx:394 -#: src/view/com/util/forms/PostDropdownBtn.tsx:396 +#: src/view/com/util/forms/PostDropdownBtn.tsx:404 +#: src/view/com/util/forms/PostDropdownBtn.tsx:406 msgid "Report post" msgstr "게시물 신고" @@ -4084,21 +4207,21 @@ msgstr "이 게시물 신고하기" msgid "Report this user" msgstr "이 사용자 신고하기" -#: src/view/com/util/post-ctrls/RepostButton.tsx:64 -#: src/view/com/util/post-ctrls/RepostButton.tsx:93 -#: src/view/com/util/post-ctrls/RepostButton.tsx:109 +#: src/view/com/util/post-ctrls/RepostButton.tsx:65 +#: src/view/com/util/post-ctrls/RepostButton.tsx:94 +#: src/view/com/util/post-ctrls/RepostButton.tsx:110 msgctxt "action" msgid "Repost" msgstr "재게시" -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:69 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:73 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:70 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:74 msgid "Repost" msgstr "재게시" -#: src/view/com/util/post-ctrls/RepostButton.tsx:85 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:46 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:92 +#: src/view/com/util/post-ctrls/RepostButton.tsx:86 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:47 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:93 msgid "Repost or quote post" msgstr "재게시 또는 게시물 인용" @@ -4106,19 +4229,19 @@ msgstr "재게시 또는 게시물 인용" msgid "Reposted By" msgstr "재게시한 사용자" -#: src/view/com/posts/FeedItem.tsx:250 +#: src/view/com/posts/FeedItem.tsx:254 msgid "Reposted by {0}" msgstr "{0} 님이 재게시함" -#: src/view/com/posts/FeedItem.tsx:265 +#: src/view/com/posts/FeedItem.tsx:269 msgid "Reposted by <0><1/>" msgstr "<0><1/> 님이 재게시함" -#: src/view/com/notifications/FeedItem.tsx:170 +#: src/view/com/notifications/FeedItem.tsx:172 msgid "reposted your post" msgstr "이(가) 내 게시물을 재게시했습니다" -#: src/view/com/post-thread/PostThreadItem.tsx:201 +#: src/view/com/post-thread/PostThreadItem.tsx:202 msgid "Reposts of this post" msgstr "이 게시물의 재게시" @@ -4132,7 +4255,7 @@ msgstr "변경 요청" msgid "Request Code" msgstr "코드 요청" -#: src/view/screens/AccessibilitySettings.tsx:82 +#: src/view/screens/AccessibilitySettings.tsx:88 msgid "Require alt text before posting" msgstr "게시하기 전 대체 텍스트 필수" @@ -4282,6 +4405,7 @@ msgid "Saves image crop settings" msgstr "이미지 자르기 설정을 저장합니다" #: src/components/dms/ChatEmptyPill.tsx:33 +#: src/components/NewskieDialog.tsx:72 msgid "Say hello!" msgstr "인사해 보세요!" @@ -4299,9 +4423,9 @@ msgstr "맨 위로 스크롤" #: src/view/com/modals/ListAddRemoveUsers.tsx:75 #: src/view/com/util/forms/SearchInput.tsx:67 #: src/view/com/util/forms/SearchInput.tsx:79 -#: src/view/screens/Search/Search.tsx:452 -#: src/view/screens/Search/Search.tsx:822 -#: src/view/screens/Search/Search.tsx:850 +#: src/view/screens/Search/Search.tsx:421 +#: src/view/screens/Search/Search.tsx:791 +#: src/view/screens/Search/Search.tsx:813 #: src/view/shell/bottom-bar/BottomBar.tsx:179 #: src/view/shell/desktop/LeftNav.tsx:343 #: src/view/shell/desktop/Search.tsx:194 @@ -4315,7 +4439,7 @@ msgstr "검색" msgid "Search for \"{query}\"" msgstr "\"{query}\"에 대한 검색 결과" -#: src/view/screens/Search/Search.tsx:906 +#: src/view/screens/Search/Search.tsx:869 msgid "Search for \"{searchText}\"" msgstr "\"{searchText}\"에 대한 검색 결과" @@ -4496,8 +4620,8 @@ msgstr "{0} 님에게 신고 보내기" msgid "Send verification email" msgstr "인증 이메일 보내기" -#: src/view/com/util/forms/PostDropdownBtn.tsx:286 -#: src/view/com/util/forms/PostDropdownBtn.tsx:289 +#: src/view/com/util/forms/PostDropdownBtn.tsx:296 +#: src/view/com/util/forms/PostDropdownBtn.tsx:299 msgid "Send via direct message" msgstr "다이렉트 메시지로 보내기" @@ -4602,11 +4726,11 @@ msgctxt "action" msgid "Share" msgstr "공유" -#: src/view/com/profile/ProfileMenu.tsx:217 -#: src/view/com/profile/ProfileMenu.tsx:226 -#: src/view/com/util/forms/PostDropdownBtn.tsx:297 -#: src/view/com/util/forms/PostDropdownBtn.tsx:306 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:297 +#: src/view/com/profile/ProfileMenu.tsx:220 +#: src/view/com/profile/ProfileMenu.tsx:229 +#: src/view/com/util/forms/PostDropdownBtn.tsx:307 +#: src/view/com/util/forms/PostDropdownBtn.tsx:316 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:300 #: src/view/screens/ProfileList.tsx:428 msgid "Share" msgstr "공유" @@ -4619,9 +4743,9 @@ msgstr "멋진 이야기를 전하세요!" msgid "Share a fun fact!" msgstr "재미있는 사실을 전하세요!" -#: src/view/com/profile/ProfileMenu.tsx:375 -#: src/view/com/util/forms/PostDropdownBtn.tsx:451 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:313 +#: src/view/com/profile/ProfileMenu.tsx:378 +#: src/view/com/util/forms/PostDropdownBtn.tsx:461 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:316 msgid "Share anyway" msgstr "무시하고 공유" @@ -4645,12 +4769,12 @@ msgstr "연결된 웹사이트를 공유합니다" #: src/components/moderation/ContentHider.tsx:116 #: src/components/moderation/LabelPreference.tsx:136 -#: src/components/moderation/PostHider.tsx:121 +#: src/components/moderation/PostHider.tsx:122 #: src/view/screens/Settings/index.tsx:381 msgid "Show" msgstr "표시" -#: src/view/com/util/post-embeds/GifEmbed.tsx:167 +#: src/view/com/util/post-embeds/GifEmbed.tsx:169 msgid "Show alt text" msgstr "대체 텍스트 표시" @@ -4668,7 +4792,7 @@ msgstr "배지 표시" msgid "Show badge and filter from feeds" msgstr "배지 표시 및 피드에서 필터링" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:211 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:215 msgid "Show follows similar to {0}" msgstr "{0} 님과 비슷한 팔로우 표시" @@ -4676,19 +4800,19 @@ msgstr "{0} 님과 비슷한 팔로우 표시" msgid "Show hidden replies" msgstr "숨겨진 답글 표시" -#: src/view/com/util/forms/PostDropdownBtn.tsx:336 -#: src/view/com/util/forms/PostDropdownBtn.tsx:338 +#: src/view/com/util/forms/PostDropdownBtn.tsx:346 +#: src/view/com/util/forms/PostDropdownBtn.tsx:348 msgid "Show less like this" msgstr "이런 항목 덜 보기" -#: src/view/com/post-thread/PostThreadItem.tsx:532 +#: src/view/com/post-thread/PostThreadItem.tsx:533 #: src/view/com/post/Post.tsx:227 -#: src/view/com/posts/FeedItem.tsx:392 +#: src/view/com/posts/FeedItem.tsx:396 msgid "Show More" msgstr "더 보기" -#: src/view/com/util/forms/PostDropdownBtn.tsx:328 -#: src/view/com/util/forms/PostDropdownBtn.tsx:330 +#: src/view/com/util/forms/PostDropdownBtn.tsx:338 +#: src/view/com/util/forms/PostDropdownBtn.tsx:340 msgid "Show more like this" msgstr "이런 항목 더 보기" @@ -4717,7 +4841,7 @@ msgid "Show Reposts" msgstr "재게시 표시" #: src/components/moderation/ContentHider.tsx:69 -#: src/components/moderation/PostHider.tsx:78 +#: src/components/moderation/PostHider.tsx:79 msgid "Show the content" msgstr "콘텐츠 표시" @@ -4818,8 +4942,10 @@ msgid "Software Dev" msgstr "소프트웨어 개발" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 +#: src/view/com/threadgate/WhoCanReply.tsx:67 +#: src/view/com/threadgate/WhoCanReply.tsx:124 msgid "Some people can reply" -msgstr "몇몇 사람들이 답글을 달 수 있음" +msgstr "일부 사람들이 답글을 달 수 있음" #: src/screens/Messages/Conversation/index.tsx:106 msgid "Something went wrong" @@ -4836,7 +4962,7 @@ msgstr "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요" msgid "Something went wrong, please try again." msgstr "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." -#: src/App.native.tsx:85 +#: src/App.native.tsx:92 #: src/App.web.tsx:74 msgid "Sorry! Your session expired. Please log in again." msgstr "죄송합니다. 세션이 만료되었습니다. 다시 로그인해 주세요." @@ -4926,9 +5052,9 @@ msgstr "이 라벨러 구독하기" msgid "Subscribe to this list" msgstr "이 리스트 구독하기" -#: src/view/screens/Search/Search.tsx:425 -msgid "Suggested Follows" -msgstr "팔로우 추천" +#: src/view/screens/Search/Explore.tsx:331 +msgid "Suggested accounts" +msgstr "추천 계정" #: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:65 msgid "Suggested for you" @@ -5029,8 +5155,8 @@ msgstr "텍스트 파일 내용:" msgid "That handle is already taken." msgstr "이 핸들은 이미 사용 중입니다." -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:305 -#: src/view/com/profile/ProfileMenu.tsx:351 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:310 +#: src/view/com/profile/ProfileMenu.tsx:354 msgid "The account will be able to interact with you after unblocking." msgstr "차단을 해제하면 이 계정이 나와 상호작용할 수 있게 됩니다." @@ -5139,17 +5265,17 @@ msgstr "신고를 전송하는 동안 문제가 발생했습니다. 인터넷 msgid "There was an issue with fetching your app passwords" msgstr "앱 비밀번호를 가져오는 동안 문제가 발생했습니다" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:103 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:125 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:139 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:107 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:129 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:143 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:99 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:111 -#: src/view/com/profile/ProfileMenu.tsx:109 -#: src/view/com/profile/ProfileMenu.tsx:120 -#: src/view/com/profile/ProfileMenu.tsx:135 -#: src/view/com/profile/ProfileMenu.tsx:146 -#: src/view/com/profile/ProfileMenu.tsx:160 -#: src/view/com/profile/ProfileMenu.tsx:173 +#: src/view/com/profile/ProfileMenu.tsx:112 +#: src/view/com/profile/ProfileMenu.tsx:123 +#: src/view/com/profile/ProfileMenu.tsx:138 +#: src/view/com/profile/ProfileMenu.tsx:149 +#: src/view/com/profile/ProfileMenu.tsx:163 +#: src/view/com/profile/ProfileMenu.tsx:176 msgid "There was an issue! {0}" msgstr "문제가 발생했습니다! {0}" @@ -5284,16 +5410,16 @@ msgstr "이 이름은 이미 사용 중입니다" msgid "This post has been deleted." msgstr "이 게시물은 삭제되었습니다." -#: src/view/com/util/forms/PostDropdownBtn.tsx:448 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:310 +#: src/view/com/util/forms/PostDropdownBtn.tsx:458 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:313 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:430 +#: src/view/com/util/forms/PostDropdownBtn.tsx:440 msgid "This post will be hidden from feeds." msgstr "이 게시물을 피드에서 숨깁니다." -#: src/view/com/profile/ProfileMenu.tsx:372 +#: src/view/com/profile/ProfileMenu.tsx:375 msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't logged in." msgstr "이 프로필은 로그인한 사용자에게만 표시됩니다. 로그인하지 않은 사용자에게는 표시되지 않습니다." @@ -5330,6 +5456,10 @@ msgstr "이 사용자는 내가 차단한 <0>{0} 리스트에 포함되어 msgid "This user is included in the <0>{0} list which you have muted." msgstr "이 사용자는 내가 뮤트한 <0>{0} 리스트에 포함되어 있습니다." +#: src/components/NewskieDialog.tsx:50 +msgid "This user is new here. Press for more info about when they joined." +msgstr "이 사용자는 새로 가입했습니다. 언제 가입했는지 자세한 정보를 보려면 누르세요." + #: src/view/com/profile/ProfileFollows.tsx:87 msgid "This user isn't following anyone." msgstr "이 사용자는 아무도 팔로우하지 않았습니다." @@ -5380,7 +5510,7 @@ msgid "Toggle to enable or disable adult content" msgstr "성인 콘텐츠 활성화 또는 비활성화 전환" #: src/screens/Hashtag.tsx:88 -#: src/view/screens/Search/Search.tsx:367 +#: src/view/screens/Search/Search.tsx:349 msgid "Top" msgstr "인기" @@ -5390,10 +5520,10 @@ msgstr "변형" #: src/components/dms/MessageMenu.tsx:103 #: src/components/dms/MessageMenu.tsx:105 -#: src/view/com/post-thread/PostThreadItem.tsx:674 -#: src/view/com/post-thread/PostThreadItem.tsx:676 -#: src/view/com/util/forms/PostDropdownBtn.tsx:267 -#: src/view/com/util/forms/PostDropdownBtn.tsx:269 +#: src/view/com/post-thread/PostThreadItem.tsx:681 +#: src/view/com/post-thread/PostThreadItem.tsx:683 +#: src/view/com/util/forms/PostDropdownBtn.tsx:277 +#: src/view/com/util/forms/PostDropdownBtn.tsx:279 msgid "Translate" msgstr "번역" @@ -5435,14 +5565,14 @@ msgstr "서비스에 연결할 수 없습니다. 인터넷 연결을 확인하 #: src/components/dms/MessagesListBlockedFooter.tsx:96 #: src/components/dms/MessagesListBlockedFooter.tsx:104 #: src/components/dms/MessagesListBlockedFooter.tsx:111 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:188 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:309 -#: src/view/com/profile/ProfileMenu.tsx:363 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:192 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:314 +#: src/view/com/profile/ProfileMenu.tsx:366 #: src/view/screens/ProfileList.tsx:626 msgid "Unblock" msgstr "차단 해제" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:193 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:197 msgctxt "action" msgid "Unblock" msgstr "차단 해제" @@ -5452,19 +5582,19 @@ msgstr "차단 해제" msgid "Unblock account" msgstr "계정 차단 해제" -#: src/view/com/profile/ProfileMenu.tsx:301 -#: src/view/com/profile/ProfileMenu.tsx:307 +#: src/view/com/profile/ProfileMenu.tsx:304 +#: src/view/com/profile/ProfileMenu.tsx:310 msgid "Unblock Account" msgstr "계정 차단 해제" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:303 -#: src/view/com/profile/ProfileMenu.tsx:345 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:308 +#: src/view/com/profile/ProfileMenu.tsx:348 msgid "Unblock Account?" msgstr "계정을 차단 해제하시겠습니까?" -#: src/view/com/util/post-ctrls/RepostButton.tsx:63 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:69 -#: src/view/com/util/post-ctrls/RepostButton.web.tsx:73 +#: src/view/com/util/post-ctrls/RepostButton.tsx:64 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:70 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:74 msgid "Undo repost" msgstr "재게시 취소" @@ -5477,12 +5607,12 @@ msgstr "언팔로우" msgid "Unfollow" msgstr "언팔로우" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:233 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:237 msgid "Unfollow {0}" msgstr "{0} 님을 언팔로우" -#: src/view/com/profile/ProfileMenu.tsx:243 -#: src/view/com/profile/ProfileMenu.tsx:253 +#: src/view/com/profile/ProfileMenu.tsx:246 +#: src/view/com/profile/ProfileMenu.tsx:256 msgid "Unfollow Account" msgstr "계정 언팔로우" @@ -5499,8 +5629,8 @@ msgstr "언뮤트" msgid "Unmute {truncatedTag}" msgstr "{truncatedTag} 언뮤트" -#: src/view/com/profile/ProfileMenu.tsx:280 -#: src/view/com/profile/ProfileMenu.tsx:286 +#: src/view/com/profile/ProfileMenu.tsx:283 +#: src/view/com/profile/ProfileMenu.tsx:289 msgid "Unmute Account" msgstr "계정 언뮤트" @@ -5512,8 +5642,8 @@ msgstr "모든 {tag} 게시물 언뮤트" msgid "Unmute conversation" msgstr "알림 언뮤트" -#: src/view/com/util/forms/PostDropdownBtn.tsx:352 -#: src/view/com/util/forms/PostDropdownBtn.tsx:357 +#: src/view/com/util/forms/PostDropdownBtn.tsx:362 +#: src/view/com/util/forms/PostDropdownBtn.tsx:367 msgid "Unmute thread" msgstr "스레드 언뮤트" @@ -5567,20 +5697,20 @@ msgstr "대신 사진 업로드하기" msgid "Upload a text file to:" msgstr "텍스트 파일 업로드 경로:" -#: src/view/com/util/UserAvatar.tsx:339 -#: src/view/com/util/UserAvatar.tsx:342 +#: src/view/com/util/UserAvatar.tsx:352 +#: src/view/com/util/UserAvatar.tsx:355 #: src/view/com/util/UserBanner.tsx:123 #: src/view/com/util/UserBanner.tsx:126 msgid "Upload from Camera" msgstr "카메라에서 업로드" -#: src/view/com/util/UserAvatar.tsx:356 +#: src/view/com/util/UserAvatar.tsx:369 #: src/view/com/util/UserBanner.tsx:140 msgid "Upload from Files" msgstr "파일에서 업로드" -#: src/view/com/util/UserAvatar.tsx:350 -#: src/view/com/util/UserAvatar.tsx:354 +#: src/view/com/util/UserAvatar.tsx:363 +#: src/view/com/util/UserAvatar.tsx:367 #: src/view/com/util/UserBanner.tsx:134 #: src/view/com/util/UserBanner.tsx:138 msgid "Upload from Library" @@ -5688,7 +5818,7 @@ msgstr "사용자 이름 또는 이메일 주소" msgid "Users" msgstr "사용자" -#: src/view/com/threadgate/WhoCanReply.tsx:143 +#: src/view/com/threadgate/WhoCanReply.tsx:274 msgid "users followed by <0/>" msgstr "<0/> 님이 팔로우한 사용자" @@ -5699,7 +5829,7 @@ msgstr "<0/> 님이 팔로우한 사용자" msgid "Users I follow" msgstr "내가 팔로우하는 사용자" -#: src/view/com/modals/Threadgate.tsx:107 +#: src/view/com/modals/Threadgate.tsx:109 msgid "Users in \"{0}\"" msgstr "\"{0}\"에 있는 사용자" @@ -5752,11 +5882,15 @@ msgstr "비디오 게임" msgid "View {0}'s avatar" msgstr "{0} 님의 아바타를 봅니다" -#: src/view/com/notifications/FeedItem.tsx:213 +#: src/view/com/notifications/FeedItem.tsx:215 msgid "View {0}'s profile" msgstr "{0} 님의 프로필 보기" -#: src/view/screens/Log.tsx:52 +#: src/components/ProfileHoverCard/index.web.tsx:430 +msgid "View blocked user's profile" +msgstr "차단한 사용자의 프로필 보기" + +#: src/view/screens/Log.tsx:56 msgid "View debug entry" msgstr "디버그 항목 보기" @@ -5768,7 +5902,7 @@ msgstr "세부 정보 보기" msgid "View details for reporting a copyright violation" msgstr "저작권 위반 신고에 대한 세부 정보 보기" -#: src/view/com/posts/FeedSlice.tsx:120 +#: src/view/com/posts/FeedSlice.tsx:124 msgid "View full thread" msgstr "전체 스레드 보기" @@ -5776,8 +5910,9 @@ msgstr "전체 스레드 보기" msgid "View information about these labels" msgstr "이 라벨에 대한 정보 보기" -#: src/components/ProfileHoverCard/index.web.tsx:396 -#: src/components/ProfileHoverCard/index.web.tsx:429 +#: src/components/ProfileHoverCard/index.web.tsx:418 +#: src/components/ProfileHoverCard/index.web.tsx:436 +#: src/components/ProfileHoverCard/index.web.tsx:463 #: src/view/com/posts/AviFollowButton.tsx:58 #: src/view/com/posts/FeedErrorMessage.tsx:174 msgid "View profile" @@ -5795,10 +5930,10 @@ msgstr "{0} 님이 제공하는 라벨링 서비스 보기" msgid "View users who like this feed" msgstr "이 피드를 좋아하는 사용자 보기" -#: src/view/com/home/HomeHeaderLayout.web.tsx:78 +#: src/view/com/home/HomeHeaderLayout.web.tsx:79 #: src/view/com/home/HomeHeaderLayoutMobile.tsx:84 msgid "View your feeds and explore more" -msgstr "" +msgstr "내 피드를 보거나 새 피드를 탐색합니다" #: src/view/com/modals/LinkWarning.tsx:89 #: src/view/com/modals/LinkWarning.tsx:95 @@ -5879,7 +6014,7 @@ msgstr "죄송하지만 이 리스트를 불러올 수 없습니다. 이 문제 msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." msgstr "죄송하지만 현재 뮤트한 단어를 불러올 수 없습니다. 다시 시도해 주세요." -#: src/view/screens/Search/Search.tsx:270 +#: src/view/screens/Search/Search.tsx:206 msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." msgstr "죄송하지만 검색을 완료할 수 없습니다. 몇 분 후에 다시 시도해 주세요." @@ -5893,8 +6028,8 @@ msgid "We're sorry! We can't find the page you were looking for." msgstr "죄송합니다. 페이지를 찾을 수 없습니다." #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:330 -msgid "We're sorry! You can only subscribe to ten labelers, and you've reached your limit of ten." -msgstr "죄송합니다. 라벨러는 10개까지만 구독할 수 있으며 10개에 도달했습니다." +msgid "We're sorry! You can only subscribe to twenty labelers, and you've reached your limit of twenty." +msgstr "죄송합니다. 라벨러는 20개까지만 구독할 수 있으며 20개에 도달했습니다." #: src/screens/Deactivated.tsx:128 msgid "Welcome back!" @@ -5923,10 +6058,20 @@ msgstr "알고리즘 피드에 어떤 언어를 표시하시겠습니까?" msgid "Who can message you?" msgstr "누구의 메시지를 허용하시겠습니까?" -#: src/view/com/modals/Threadgate.tsx:67 +#: src/view/com/modals/Threadgate.tsx:69 +#: src/view/com/threadgate/WhoCanReply.tsx:73 +#: src/view/com/threadgate/WhoCanReply.tsx:130 msgid "Who can reply" msgstr "답글을 달 수 있는 사람" +#: src/view/com/threadgate/WhoCanReply.tsx:206 +msgid "Who can reply dialog" +msgstr "답글을 달 수 있는 사람 대화 상자" + +#: src/view/com/threadgate/WhoCanReply.tsx:210 +msgid "Who can reply?" +msgstr "누가 답글을 달 수 있나요?" + #: src/screens/Home/NoFeedsPinned.tsx:79 #: src/screens/Messages/List/index.tsx:185 msgid "Whoops!" @@ -5965,7 +6110,7 @@ msgstr "가로" msgid "Write a message" msgstr "메시지를 입력하세요" -#: src/view/com/composer/Composer.tsx:549 +#: src/view/com/composer/Composer.tsx:551 msgid "Write post" msgstr "게시물 작성" @@ -6041,7 +6186,7 @@ msgstr "팔로워가 없습니다." #: src/screens/Profile/KnownFollowers.tsx:99 msgid "You don't follow any users who follow @{name}." -msgstr "" +msgstr "@{name} 님을 팔로우하는 사용자를 팔로우하고 있지 않습니다." #: 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." @@ -6146,11 +6291,11 @@ msgstr "신고하려면 하나 이상의 라벨을 선택해야 합니다." msgid "You previously deactivated @{0}." msgstr "이전에 @{0}을(를) 비활성화했습니다." -#: src/view/com/util/forms/PostDropdownBtn.tsx:168 +#: src/view/com/util/forms/PostDropdownBtn.tsx:174 msgid "You will no longer receive notifications for this thread" msgstr "이 스레드에 대한 알림을 더 이상 받지 않습니다" -#: src/view/com/util/forms/PostDropdownBtn.tsx:171 +#: src/view/com/util/forms/PostDropdownBtn.tsx:170 msgid "You will now receive notifications for this thread" msgstr "이제 이 스레드에 대한 알림을 받습니다" From 55812b03940852f1f91cd0a46b5c093601c854a9 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Fri, 21 Jun 2024 12:41:06 -0400 Subject: [PATCH 084/127] Bsky short link service (#4542) * bskylink: scaffold service w/ initial config and schema * bskylink: implement link creation and redirects * bskylink: tidy * bskylink: tests * bskylink: tidy, add error handler * bskylink: add dockerfile * bskylink: add build * bskylink: fix some express plumbing * bskyweb: proxy fallthrough routes to link service redirects * bskyweb: build w/ link proxy * Add AASA to bskylink (#4588) --------- Co-authored-by: Hailey --- .../workflows/build-and-push-bskyweb-aws.yaml | 1 + .../workflows/build-and-push-link-aws.yaml | 55 + Dockerfile.bskylink | 41 + bskylink/package.json | 26 + bskylink/src/bin.ts | 24 + bskylink/src/config.ts | 82 ++ bskylink/src/context.ts | 33 + bskylink/src/db/index.ts | 174 +++ bskylink/src/db/migrations/001-init.ts | 15 + bskylink/src/db/migrations/index.ts | 5 + bskylink/src/db/migrations/provider.ts | 8 + bskylink/src/db/schema.ts | 17 + bskylink/src/index.ts | 45 + bskylink/src/logger.ts | 4 + bskylink/src/routes/create.ts | 111 ++ bskylink/src/routes/health.ts | 20 + bskylink/src/routes/index.ts | 17 + bskylink/src/routes/redirect.ts | 40 + bskylink/src/routes/siteAssociation.ts | 13 + bskylink/src/routes/util.ts | 23 + bskylink/src/util.ts | 8 + bskylink/tests/index.ts | 84 ++ bskylink/tests/infra/_common.sh | 157 +++ bskylink/tests/infra/docker-compose.yaml | 27 + bskylink/tests/infra/with-test-db.sh | 9 + bskylink/tsconfig.json | 10 + bskylink/yarn.lock | 1027 +++++++++++++++++ bskyweb/cmd/bskyweb/main.go | 9 +- bskyweb/cmd/bskyweb/server.go | 34 + 29 files changed, 2118 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-and-push-link-aws.yaml create mode 100644 Dockerfile.bskylink create mode 100644 bskylink/package.json create mode 100644 bskylink/src/bin.ts create mode 100644 bskylink/src/config.ts create mode 100644 bskylink/src/context.ts create mode 100644 bskylink/src/db/index.ts create mode 100644 bskylink/src/db/migrations/001-init.ts create mode 100644 bskylink/src/db/migrations/index.ts create mode 100644 bskylink/src/db/migrations/provider.ts create mode 100644 bskylink/src/db/schema.ts create mode 100644 bskylink/src/index.ts create mode 100644 bskylink/src/logger.ts create mode 100644 bskylink/src/routes/create.ts create mode 100644 bskylink/src/routes/health.ts create mode 100644 bskylink/src/routes/index.ts create mode 100644 bskylink/src/routes/redirect.ts create mode 100644 bskylink/src/routes/siteAssociation.ts create mode 100644 bskylink/src/routes/util.ts create mode 100644 bskylink/src/util.ts create mode 100644 bskylink/tests/index.ts create mode 100755 bskylink/tests/infra/_common.sh create mode 100644 bskylink/tests/infra/docker-compose.yaml create mode 100755 bskylink/tests/infra/with-test-db.sh create mode 100644 bskylink/tsconfig.json create mode 100644 bskylink/yarn.lock diff --git a/.github/workflows/build-and-push-bskyweb-aws.yaml b/.github/workflows/build-and-push-bskyweb-aws.yaml index 6eb9485b14..bcd759b0ce 100644 --- a/.github/workflows/build-and-push-bskyweb-aws.yaml +++ b/.github/workflows/build-and-push-bskyweb-aws.yaml @@ -4,6 +4,7 @@ on: push: branches: - main + - divy/bskylink env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} diff --git a/.github/workflows/build-and-push-link-aws.yaml b/.github/workflows/build-and-push-link-aws.yaml new file mode 100644 index 0000000000..f91af48770 --- /dev/null +++ b/.github/workflows/build-and-push-link-aws.yaml @@ -0,0 +1,55 @@ +name: build-and-push-link-aws +on: + push: + branches: + - divy/bskylink + +env: + REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} + USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }} + PASSWORD: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_PASSWORD }} + IMAGE_NAME: bskylink + +jobs: + link-container-aws: + if: github.repository == 'bluesky-social/social-app' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v1 + + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.USERNAME}} + password: ${{ env.PASSWORD }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=sha,enable=true,priority=100,prefix=,suffix=,format=long + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v4 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + file: ./Dockerfile.bskylink + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile.bskylink b/Dockerfile.bskylink new file mode 100644 index 0000000000..acde232256 --- /dev/null +++ b/Dockerfile.bskylink @@ -0,0 +1,41 @@ +FROM node:20.11-alpine3.18 as build + +# Move files into the image and install +WORKDIR /app + +COPY ./bskylink/package.json ./ +COPY ./bskylink/yarn.lock ./ +RUN yarn install --frozen-lockfile + +COPY ./bskylink ./ + +# build then prune dev deps +RUN yarn build +RUN yarn install --production --ignore-scripts --prefer-offline + +# Uses assets from build stage to reduce build size +FROM node:20.11-alpine3.18 + +RUN apk add --update dumb-init + +# Avoid zombie processes, handle signal forwarding +ENTRYPOINT ["dumb-init", "--"] + +WORKDIR /app +COPY --from=build /app /app +RUN mkdir /app/data && chown node /app/data + +VOLUME /app/data +EXPOSE 3000 +ENV LINK_PORT=3000 +ENV NODE_ENV=production +# potential perf issues w/ io_uring on this version of node +ENV UV_USE_IO_URING=0 + +# https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user +USER node +CMD ["node", "--heapsnapshot-signal=SIGUSR2", "--enable-source-maps", "dist/bin.js"] + +LABEL org.opencontainers.image.source=https://github.com/bluesky-social/social-app +LABEL org.opencontainers.image.description="Bsky Link Service" +LABEL org.opencontainers.image.licenses=UNLICENSED diff --git a/bskylink/package.json b/bskylink/package.json new file mode 100644 index 0000000000..5fdee206b4 --- /dev/null +++ b/bskylink/package.json @@ -0,0 +1,26 @@ +{ + "name": "bskylink", + "version": "0.0.0", + "type": "module", + "main": "index.ts", + "scripts": { + "test": "./tests/infra/with-test-db.sh node --loader ts-node/esm --test ./tests/index.ts", + "build": "tsc" + }, + "dependencies": { + "@atproto/common": "^0.4.0", + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.19.2", + "http-terminator": "^3.2.0", + "kysely": "^0.27.3", + "pg": "^8.12.0", + "pino": "^9.2.0", + "uint8arrays": "^5.1.0" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/pg": "^8.11.6", + "typescript": "^5.4.5" + } +} diff --git a/bskylink/src/bin.ts b/bskylink/src/bin.ts new file mode 100644 index 0000000000..17f068841b --- /dev/null +++ b/bskylink/src/bin.ts @@ -0,0 +1,24 @@ +import {Database, envToCfg, httpLogger, LinkService, readEnv} from './index.js' + +async function main() { + const env = readEnv() + const cfg = envToCfg(env) + if (cfg.db.migrationUrl) { + const migrateDb = Database.postgres({ + url: cfg.db.migrationUrl, + schema: cfg.db.schema, + }) + await migrateDb.migrateToLatestOrThrow() + await migrateDb.close() + } + const link = await LinkService.create(cfg) + await link.start() + httpLogger.info('link service is running') + process.on('SIGTERM', async () => { + httpLogger.info('link service is stopping') + await link.destroy() + httpLogger.info('link service is stopped') + }) +} + +main() diff --git a/bskylink/src/config.ts b/bskylink/src/config.ts new file mode 100644 index 0000000000..ce409cccca --- /dev/null +++ b/bskylink/src/config.ts @@ -0,0 +1,82 @@ +import {envInt, envList, envStr} from '@atproto/common' + +export type Config = { + service: ServiceConfig + db: DbConfig +} + +export type ServiceConfig = { + port: number + version?: string + hostnames: string[] + appHostname: string +} + +export type DbConfig = { + url: string + migrationUrl?: string + pool: DbPoolConfig + schema?: string +} + +export type DbPoolConfig = { + size: number + maxUses: number + idleTimeoutMs: number +} + +export type Environment = { + port?: number + version?: string + hostnames: string[] + appHostname?: string + dbPostgresUrl?: string + dbPostgresMigrationUrl?: string + dbPostgresSchema?: string + dbPostgresPoolSize?: number + dbPostgresPoolMaxUses?: number + dbPostgresPoolIdleTimeoutMs?: number +} + +export const readEnv = (): Environment => { + return { + port: envInt('LINK_PORT'), + version: envStr('LINK_VERSION'), + hostnames: envList('LINK_HOSTNAMES'), + appHostname: envStr('LINK_APP_HOSTNAME'), + dbPostgresUrl: envStr('LINK_DB_POSTGRES_URL'), + dbPostgresMigrationUrl: envStr('LINK_DB_POSTGRES_MIGRATION_URL'), + dbPostgresSchema: envStr('LINK_DB_POSTGRES_SCHEMA'), + dbPostgresPoolSize: envInt('LINK_DB_POSTGRES_POOL_SIZE'), + dbPostgresPoolMaxUses: envInt('LINK_DB_POSTGRES_POOL_MAX_USES'), + dbPostgresPoolIdleTimeoutMs: envInt( + 'LINK_DB_POSTGRES_POOL_IDLE_TIMEOUT_MS', + ), + } +} + +export const envToCfg = (env: Environment): Config => { + const serviceCfg: ServiceConfig = { + port: env.port ?? 3000, + version: env.version, + hostnames: env.hostnames, + appHostname: env.appHostname || 'bsky.app', + } + if (!env.dbPostgresUrl) { + throw new Error('Must configure postgres url (LINK_DB_POSTGRES_URL)') + } + const dbCfg: DbConfig = { + url: env.dbPostgresUrl, + migrationUrl: env.dbPostgresMigrationUrl, + schema: env.dbPostgresSchema, + pool: { + idleTimeoutMs: env.dbPostgresPoolIdleTimeoutMs ?? 10000, + maxUses: env.dbPostgresPoolMaxUses ?? Infinity, + size: env.dbPostgresPoolSize ?? 10, + }, + } + return { + service: serviceCfg, + db: dbCfg, + } +} diff --git a/bskylink/src/context.ts b/bskylink/src/context.ts new file mode 100644 index 0000000000..7e6f2f34e8 --- /dev/null +++ b/bskylink/src/context.ts @@ -0,0 +1,33 @@ +import {Config} from './config.js' +import Database from './db/index.js' + +export type AppContextOptions = { + cfg: Config + db: Database +} + +export class AppContext { + cfg: Config + db: Database + abortController = new AbortController() + + constructor(private opts: AppContextOptions) { + this.cfg = this.opts.cfg + this.db = this.opts.db + } + + static async fromConfig(cfg: Config, overrides?: Partial) { + const db = Database.postgres({ + url: cfg.db.url, + schema: cfg.db.schema, + poolSize: cfg.db.pool.size, + poolMaxUses: cfg.db.pool.maxUses, + poolIdleTimeoutMs: cfg.db.pool.idleTimeoutMs, + }) + return new AppContext({ + cfg, + db, + ...overrides, + }) + } +} diff --git a/bskylink/src/db/index.ts b/bskylink/src/db/index.ts new file mode 100644 index 0000000000..5f201cc07d --- /dev/null +++ b/bskylink/src/db/index.ts @@ -0,0 +1,174 @@ +import assert from 'assert' +import { + Kysely, + KyselyPlugin, + Migrator, + PluginTransformQueryArgs, + PluginTransformResultArgs, + PostgresDialect, + QueryResult, + RootOperationNode, + UnknownRow, +} from 'kysely' +import {default as Pg} from 'pg' + +import {dbLogger as log} from '../logger.js' +import {default as migrations} from './migrations/index.js' +import {DbMigrationProvider} from './migrations/provider.js' +import {DbSchema} from './schema.js' + +export class Database { + migrator: Migrator + destroyed = false + + constructor(public db: Kysely, public cfg: PgConfig) { + this.migrator = new Migrator({ + db, + migrationTableSchema: cfg.schema, + provider: new DbMigrationProvider(migrations), + }) + } + + static postgres(opts: PgOptions): Database { + const {schema, url, txLockNonce} = opts + const pool = + opts.pool ?? + new Pg.Pool({ + connectionString: url, + max: opts.poolSize, + maxUses: opts.poolMaxUses, + idleTimeoutMillis: opts.poolIdleTimeoutMs, + }) + + // Select count(*) and other pg bigints as js integer + Pg.types.setTypeParser(Pg.types.builtins.INT8, n => parseInt(n, 10)) + + // Setup schema usage, primarily for test parallelism (each test suite runs in its own pg schema) + if (schema && !/^[a-z_]+$/i.test(schema)) { + throw new Error(`Postgres schema must only contain [A-Za-z_]: ${schema}`) + } + + pool.on('error', onPoolError) + + const db = new Kysely({ + dialect: new PostgresDialect({pool}), + }) + + return new Database(db, { + pool, + schema, + url, + txLockNonce, + }) + } + + async transaction(fn: (db: Database) => Promise): Promise { + const leakyTxPlugin = new LeakyTxPlugin() + return this.db + .withPlugin(leakyTxPlugin) + .transaction() + .execute(txn => { + const dbTxn = new Database(txn, this.cfg) + return fn(dbTxn) + .catch(async err => { + leakyTxPlugin.endTx() + // ensure that all in-flight queries are flushed & the connection is open + await dbTxn.db.getExecutor().provideConnection(async () => {}) + throw err + }) + .finally(() => leakyTxPlugin.endTx()) + }) + } + + get schema(): string | undefined { + return this.cfg.schema + } + + get isTransaction() { + return this.db.isTransaction + } + + assertTransaction() { + assert(this.isTransaction, 'Transaction required') + } + + assertNotTransaction() { + assert(!this.isTransaction, 'Cannot be in a transaction') + } + + async close(): Promise { + if (this.destroyed) return + await this.db.destroy() + this.destroyed = true + } + + async migrateToOrThrow(migration: string) { + if (this.schema) { + await this.db.schema.createSchema(this.schema).ifNotExists().execute() + } + const {error, results} = await this.migrator.migrateTo(migration) + if (error) { + throw error + } + if (!results) { + throw new Error('An unknown failure occurred while migrating') + } + return results + } + + async migrateToLatestOrThrow() { + if (this.schema) { + await this.db.schema.createSchema(this.schema).ifNotExists().execute() + } + const {error, results} = await this.migrator.migrateToLatest() + if (error) { + throw error + } + if (!results) { + throw new Error('An unknown failure occurred while migrating') + } + return results + } +} + +export default Database + +export type PgConfig = { + pool: Pg.Pool + url: string + schema?: string + txLockNonce?: string +} + +type PgOptions = { + url: string + pool?: Pg.Pool + schema?: string + poolSize?: number + poolMaxUses?: number + poolIdleTimeoutMs?: number + txLockNonce?: string +} + +class LeakyTxPlugin implements KyselyPlugin { + private txOver = false + + endTx() { + this.txOver = true + } + + transformQuery(args: PluginTransformQueryArgs): RootOperationNode { + if (this.txOver) { + throw new Error('tx already failed') + } + return args.node + } + + async transformResult( + args: PluginTransformResultArgs, + ): Promise> { + return args.result + } +} + +const onPoolError = (err: Error) => log.error({err}, 'db pool error') diff --git a/bskylink/src/db/migrations/001-init.ts b/bskylink/src/db/migrations/001-init.ts new file mode 100644 index 0000000000..fe3bcf1867 --- /dev/null +++ b/bskylink/src/db/migrations/001-init.ts @@ -0,0 +1,15 @@ +import {Kysely} from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('link') + .addColumn('id', 'varchar', col => col.primaryKey()) + .addColumn('type', 'smallint', col => col.notNull()) // integer enum: 1->starterpack + .addColumn('path', 'varchar', col => col.notNull()) + .addUniqueConstraint('link_path_unique', ['path']) + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('link').execute() +} diff --git a/bskylink/src/db/migrations/index.ts b/bskylink/src/db/migrations/index.ts new file mode 100644 index 0000000000..05e4de9377 --- /dev/null +++ b/bskylink/src/db/migrations/index.ts @@ -0,0 +1,5 @@ +import * as init from './001-init.js' + +export default { + '001': init, +} diff --git a/bskylink/src/db/migrations/provider.ts b/bskylink/src/db/migrations/provider.ts new file mode 100644 index 0000000000..bef93a48fd --- /dev/null +++ b/bskylink/src/db/migrations/provider.ts @@ -0,0 +1,8 @@ +import {Migration, MigrationProvider} from 'kysely' + +export class DbMigrationProvider implements MigrationProvider { + constructor(private migrations: Record) {} + async getMigrations(): Promise> { + return this.migrations + } +} diff --git a/bskylink/src/db/schema.ts b/bskylink/src/db/schema.ts new file mode 100644 index 0000000000..8d97f58005 --- /dev/null +++ b/bskylink/src/db/schema.ts @@ -0,0 +1,17 @@ +import {Selectable} from 'kysely' + +export type DbSchema = { + link: Link +} + +export interface Link { + id: string + type: LinkType + path: string +} + +export enum LinkType { + StarterPack = 1, +} + +export type LinkEntry = Selectable diff --git a/bskylink/src/index.ts b/bskylink/src/index.ts new file mode 100644 index 0000000000..ca425eee8c --- /dev/null +++ b/bskylink/src/index.ts @@ -0,0 +1,45 @@ +import events from 'node:events' +import http from 'node:http' + +import cors from 'cors' +import express from 'express' +import {createHttpTerminator, HttpTerminator} from 'http-terminator' + +import {Config} from './config.js' +import {AppContext} from './context.js' +import {default as routes, errorHandler} from './routes/index.js' + +export * from './config.js' +export * from './db/index.js' +export * from './logger.js' + +export class LinkService { + public server?: http.Server + private terminator?: HttpTerminator + + constructor(public app: express.Application, public ctx: AppContext) {} + + static async create(cfg: Config): Promise { + let app = express() + app.use(cors()) + + const ctx = await AppContext.fromConfig(cfg) + app = routes(ctx, app) + app.use(errorHandler) + + return new LinkService(app, ctx) + } + + async start() { + this.server = this.app.listen(this.ctx.cfg.service.port) + this.server.keepAliveTimeout = 90000 + this.terminator = createHttpTerminator({server: this.server}) + await events.once(this.server, 'listening') + } + + async destroy() { + this.ctx.abortController.abort() + await this.terminator?.terminate() + await this.ctx.db.close() + } +} diff --git a/bskylink/src/logger.ts b/bskylink/src/logger.ts new file mode 100644 index 0000000000..25bb590a1d --- /dev/null +++ b/bskylink/src/logger.ts @@ -0,0 +1,4 @@ +import {subsystemLogger} from '@atproto/common' + +export const httpLogger = subsystemLogger('bskylink') +export const dbLogger = subsystemLogger('bskylink:db') diff --git a/bskylink/src/routes/create.ts b/bskylink/src/routes/create.ts new file mode 100644 index 0000000000..db7c3f8090 --- /dev/null +++ b/bskylink/src/routes/create.ts @@ -0,0 +1,111 @@ +import assert from 'node:assert' + +import bodyParser from 'body-parser' +import {Express, Request} from 'express' + +import {AppContext} from '../context.js' +import {LinkType} from '../db/schema.js' +import {randomId} from '../util.js' +import {handler} from './util.js' + +export default function (ctx: AppContext, app: Express) { + return app.post( + '/link', + bodyParser.json(), + handler(async (req, res) => { + let path: string + if (typeof req.body?.path === 'string') { + path = req.body.path + } else { + return res.status(400).json({ + error: 'InvalidPath', + message: '"path" parameter is missing or not a string', + }) + } + if (!path.startsWith('/')) { + return res.status(400).json({ + error: 'InvalidPath', + message: + '"path" parameter must be formatted as a path, starting with a "/"', + }) + } + const parts = getPathParts(path) + if (parts.length === 3 && parts[0] === 'start') { + // link pattern: /start/{did}/{rkey} + if (!parts[1].startsWith('did:')) { + // enforce strong links + return res.status(400).json({ + error: 'InvalidPath', + message: + '"path" parameter for starter pack must contain the actor\'s DID', + }) + } + const id = await ensureLink(ctx, LinkType.StarterPack, parts) + return res.json({url: getUrl(ctx, req, id)}) + } + return res.status(400).json({ + error: 'InvalidPath', + message: '"path" parameter does not have a known format', + }) + }), + ) +} + +const ensureLink = async (ctx: AppContext, type: LinkType, parts: string[]) => { + const normalizedPath = normalizedPathFromParts(parts) + const created = await ctx.db.db + .insertInto('link') + .values({ + id: randomId(), + type, + path: normalizedPath, + }) + .onConflict(oc => oc.column('path').doNothing()) + .returningAll() + .executeTakeFirst() + if (created) { + return created.id + } + const found = await ctx.db.db + .selectFrom('link') + .selectAll() + .where('path', '=', normalizedPath) + .executeTakeFirstOrThrow() + return found.id +} + +const getUrl = (ctx: AppContext, req: Request, id: string) => { + if (!ctx.cfg.service.hostnames.length) { + assert(req.headers.host, 'request must be made with host header') + const baseUrl = + req.protocol === 'http' && req.headers.host.startsWith('localhost:') + ? `http://${req.headers.host}` + : `https://${req.headers.host}` + return `${baseUrl}/${id}` + } + const baseUrl = ctx.cfg.service.hostnames.includes(req.headers.host) + ? `https://${req.headers.host}` + : `https://${ctx.cfg.service.hostnames[0]}` + return `${baseUrl}/${id}` +} + +const normalizedPathFromParts = (parts: string[]): string => { + return ( + '/' + + parts + .map(encodeURIComponent) + .map(part => part.replaceAll('%3A', ':')) // preserve colons + .join('/') + ) +} + +const getPathParts = (path: string): string[] => { + if (path === '/') return [] + if (path.endsWith('/')) { + path = path.slice(0, -1) // ignore trailing slash + } + return path + .slice(1) // remove leading slash + .split('/') + .map(decodeURIComponent) +} diff --git a/bskylink/src/routes/health.ts b/bskylink/src/routes/health.ts new file mode 100644 index 0000000000..c8a30c59ea --- /dev/null +++ b/bskylink/src/routes/health.ts @@ -0,0 +1,20 @@ +import {Express} from 'express' +import {sql} from 'kysely' + +import {AppContext} from '../context.js' +import {handler} from './util.js' + +export default function (ctx: AppContext, app: Express) { + return app.get( + '/_health', + handler(async (_req, res) => { + const {version} = ctx.cfg.service + try { + await sql`select 1`.execute(ctx.db.db) + return res.send({version}) + } catch (err) { + return res.status(503).send({version, error: 'Service Unavailable'}) + } + }), + ) +} diff --git a/bskylink/src/routes/index.ts b/bskylink/src/routes/index.ts new file mode 100644 index 0000000000..f60b99bcb7 --- /dev/null +++ b/bskylink/src/routes/index.ts @@ -0,0 +1,17 @@ +import {Express} from 'express' + +import {AppContext} from '../context.js' +import {default as create} from './create.js' +import {default as health} from './health.js' +import {default as redirect} from './redirect.js' +import {default as siteAssociation} from './siteAssociation.js' + +export * from './util.js' + +export default function (ctx: AppContext, app: Express) { + app = health(ctx, app) // GET /_health + app = siteAssociation(ctx, app) // GET /.well-known/apple-app-site-association + app = create(ctx, app) // POST /link + app = redirect(ctx, app) // GET /:linkId (should go last due to permissive matching) + return app +} diff --git a/bskylink/src/routes/redirect.ts b/bskylink/src/routes/redirect.ts new file mode 100644 index 0000000000..7791ea815e --- /dev/null +++ b/bskylink/src/routes/redirect.ts @@ -0,0 +1,40 @@ +import assert from 'node:assert' + +import {DAY, SECOND} from '@atproto/common' +import {Express} from 'express' + +import {AppContext} from '../context.js' +import {handler} from './util.js' + +export default function (ctx: AppContext, app: Express) { + return app.get( + '/:linkId', + handler(async (req, res) => { + const linkId = req.params.linkId + assert( + typeof linkId === 'string', + 'express guarantees id parameter is a string', + ) + const found = await ctx.db.db + .selectFrom('link') + .selectAll() + .where('id', '=', linkId) + .executeTakeFirst() + if (!found) { + // potentially broken or mistyped link— send user to the app + res.setHeader('Location', `https://${ctx.cfg.service.appHostname}`) + res.setHeader('Cache-Control', 'no-store') + return res.status(302).end() + } + // build url from original url in order to preserve query params + const url = new URL( + req.originalUrl, + `https://${ctx.cfg.service.appHostname}`, + ) + url.pathname = found.path + res.setHeader('Location', url.href) + res.setHeader('Cache-Control', `max-age=${(7 * DAY) / SECOND}`) + return res.status(301).end() + }), + ) +} diff --git a/bskylink/src/routes/siteAssociation.ts b/bskylink/src/routes/siteAssociation.ts new file mode 100644 index 0000000000..ae3b42e304 --- /dev/null +++ b/bskylink/src/routes/siteAssociation.ts @@ -0,0 +1,13 @@ +import {Express} from 'express' + +import {AppContext} from '../context.js' + +export default function (ctx: AppContext, app: Express) { + return app.get('/.well-known/apple-app-site-association', (req, res) => { + res.json({ + appclips: { + apps: ['B3LX46C5HS.xyz.blueskyweb.app.AppClip'], + }, + }) + }) +} diff --git a/bskylink/src/routes/util.ts b/bskylink/src/routes/util.ts new file mode 100644 index 0000000000..bcac64b01d --- /dev/null +++ b/bskylink/src/routes/util.ts @@ -0,0 +1,23 @@ +import {ErrorRequestHandler, Request, RequestHandler, Response} from 'express' + +import {httpLogger} from '../logger.js' + +export type Handler = (req: Request, res: Response) => Awaited + +export const handler = (runHandler: Handler): RequestHandler => { + return async (req, res, next) => { + try { + await runHandler(req, res) + } catch (err) { + next(err) + } + } +} + +export const errorHandler: ErrorRequestHandler = (err, _req, res, next) => { + httpLogger.error({err}, 'request error') + if (res.headersSent) { + return next(err) + } + return res.status(500).end('server error') +} diff --git a/bskylink/src/util.ts b/bskylink/src/util.ts new file mode 100644 index 0000000000..0b57dd5c5b --- /dev/null +++ b/bskylink/src/util.ts @@ -0,0 +1,8 @@ +import {randomBytes} from 'node:crypto' + +import {toString} from 'uint8arrays' + +// 40bit random id of 5-7 characters +export const randomId = () => { + return toString(randomBytes(5), 'base58btc') +} diff --git a/bskylink/tests/index.ts b/bskylink/tests/index.ts new file mode 100644 index 0000000000..51449c21be --- /dev/null +++ b/bskylink/tests/index.ts @@ -0,0 +1,84 @@ +import assert from 'node:assert' +import {AddressInfo} from 'node:net' +import {after, before, describe, it} from 'node:test' + +import {Database, envToCfg, LinkService, readEnv} from '../src/index.js' + +describe('link service', async () => { + let linkService: LinkService + let baseUrl: string + before(async () => { + const env = readEnv() + const cfg = envToCfg({ + ...env, + hostnames: ['test.bsky.link'], + appHostname: 'test.bsky.app', + dbPostgresSchema: 'link_test', + dbPostgresUrl: process.env.DB_POSTGRES_URL, + }) + const migrateDb = Database.postgres({ + url: cfg.db.url, + schema: cfg.db.schema, + }) + await migrateDb.migrateToLatestOrThrow() + await migrateDb.close() + linkService = await LinkService.create(cfg) + await linkService.start() + const {port} = linkService.server?.address() as AddressInfo + baseUrl = `http://localhost:${port}` + }) + + after(async () => { + await linkService?.destroy() + }) + + it('creates a starter pack link', async () => { + const link = await getLink('/start/did:example:alice/xxx') + const url = new URL(link) + assert.strictEqual(url.origin, 'https://test.bsky.link') + assert.match(url.pathname, /^\/[a-z0-9]+$/i) + }) + + it('normalizes input paths and provides same link each time.', async () => { + const link1 = await getLink('/start/did%3Aexample%3Abob/yyy') + const link2 = await getLink('/start/did:example:bob/yyy/') + assert.strictEqual(link1, link2) + }) + + it('serves permanent redirect, preserving query params.', async () => { + const link = await getLink('/start/did:example:carol/zzz/') + const [status, location] = await getRedirect(`${link}?a=b`) + assert.strictEqual(status, 301) + const locationUrl = new URL(location) + assert.strictEqual( + locationUrl.pathname + locationUrl.search, + '/start/did:example:carol/zzz?a=b', + ) + }) + + async function getRedirect(link: string): Promise<[number, string]> { + const url = new URL(link) + const base = new URL(baseUrl) + url.protocol = base.protocol + url.host = base.host + const res = await fetch(url, {redirect: 'manual'}) + await res.arrayBuffer() // drain + assert( + res.status === 301 || res.status === 303, + 'response was not a redirect', + ) + return [res.status, res.headers.get('location') ?? ''] + } + + async function getLink(path: string): Promise { + const res = await fetch(new URL('/link', baseUrl), { + method: 'post', + headers: {'content-type': 'application/json'}, + body: JSON.stringify({path}), + }) + assert.strictEqual(res.status, 200) + const payload = await res.json() + assert(typeof payload.url === 'string') + return payload.url + } +}) diff --git a/bskylink/tests/infra/_common.sh b/bskylink/tests/infra/_common.sh new file mode 100755 index 0000000000..1587f5c70a --- /dev/null +++ b/bskylink/tests/infra/_common.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env sh + +# Exit if any command fails +set -e + +get_container_id() { + local compose_file=$1 + local service=$2 + if [ -z "${compose_file}" ] || [ -z "${service}" ]; then + echo "usage: get_container_id " + exit 1 + fi + + # first line of jq normalizes for docker compose breaking change, see docker/compose#10958 + docker compose --file $compose_file ps --format json --status running \ + | jq -sc '.[] | if type=="array" then .[] else . end' | jq -s \ + | jq -r '.[]? | select(.Service == "'${service}'") | .ID' +} + +# Exports all environment variables +export_env() { + export_pg_env +} + +# Exports postgres environment variables +export_pg_env() { + # Based on creds in compose.yaml + export PGPORT=5433 + export PGHOST=localhost + export PGUSER=pg + export PGPASSWORD=password + export PGDATABASE=postgres + export DB_POSTGRES_URL="postgresql://pg:password@127.0.0.1:5433/postgres" +} + + +pg_clear() { + local pg_uri=$1 + + for schema_name in `psql "${pg_uri}" -c "SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT LIKE 'pg_%' AND schema_name NOT LIKE 'information_schema';" -t`; do + psql "${pg_uri}" -c "DROP SCHEMA \"${schema_name}\" CASCADE;" + done +} + +pg_init() { + local pg_uri=$1 + + psql "${pg_uri}" -c "CREATE SCHEMA IF NOT EXISTS \"public\";" +} + +main_native() { + local services=${SERVICES} + local postgres_url_env_var=`[[ $services == *"db_test"* ]] && echo "DB_TEST_POSTGRES_URL" || echo "DB_POSTGRES_URL"` + + postgres_url="${!postgres_url_env_var}" + + if [ -n "${postgres_url}" ]; then + echo "Using ${postgres_url_env_var} (${postgres_url}) to connect to postgres." + pg_init "${postgres_url}" + else + echo "Postgres connection string missing did you set ${postgres_url_env_var}?" + exit 1 + fi + + cleanup() { + local services=$@ + + if [ -n "${postgres_url}" ] && [[ $services == *"db_test"* ]]; then + pg_clear "${postgres_url}" &> /dev/null + fi + } + + # trap SIGINT and performs cleanup + trap "on_sigint ${services}" INT + on_sigint() { + cleanup $@ + exit $? + } + + # Run the arguments as a command + DB_POSTGRES_URL="${postgres_url}" \ + "$@" + code=$? + + cleanup ${services} + + exit ${code} +} + +main_docker() { + # Expect a SERVICES env var to be set with the docker service names + local services=${SERVICES} + + dir=$(dirname $0) + compose_file="${dir}/docker-compose.yaml" + + # whether this particular script started the container(s) + started_container=false + + # performs cleanup as necessary, i.e. taking down containers + # if this script started them + cleanup() { + local services=$@ + echo # newline + if $started_container; then + docker compose --file $compose_file rm --force --stop --volumes ${services} + fi + } + + # trap SIGINT and performs cleanup + trap "on_sigint ${services}" INT + on_sigint() { + cleanup $@ + exit $? + } + + # check if all services are running already + not_running=false + for service in $services; do + container_id=$(get_container_id $compose_file $service) + if [ -z $container_id ]; then + not_running=true + break + fi + done + + # if any are missing, recreate all services + if $not_running; then + started_container=true + docker compose --file $compose_file up --wait --force-recreate ${services} + else + echo "all services ${services} are already running" + fi + + # do not exit when following commands fail, so we can intercept exit code & tear down docker + set +e + + # setup environment variables and run args + export_env + "$@" + # save return code for later + code=$? + + # performs cleanup as necessary + cleanup ${services} + exit ${code} +} + +# Main entry point +main() { + if ! docker ps >/dev/null 2>&1; then + echo "Docker unavailable. Running on host." + main_native $@ + else + main_docker $@ + fi +} diff --git a/bskylink/tests/infra/docker-compose.yaml b/bskylink/tests/infra/docker-compose.yaml new file mode 100644 index 0000000000..4bc939e01c --- /dev/null +++ b/bskylink/tests/infra/docker-compose.yaml @@ -0,0 +1,27 @@ +version: '3.8' +services: + # An ephermerally-stored postgres database for single-use test runs + db_test: &db_test + image: postgres:14.11-alpine + environment: + - POSTGRES_USER=pg + - POSTGRES_PASSWORD=password + ports: + - '5433:5432' + # Healthcheck ensures db is queryable when `docker-compose up --wait` completes + healthcheck: + test: 'pg_isready -U pg' + interval: 500ms + timeout: 10s + retries: 20 + # A persistently-stored postgres database + db: + <<: *db_test + ports: + - '5432:5432' + healthcheck: + disable: true + volumes: + - link_db:/var/lib/postgresql/data +volumes: + link_db: diff --git a/bskylink/tests/infra/with-test-db.sh b/bskylink/tests/infra/with-test-db.sh new file mode 100755 index 0000000000..cc083491a5 --- /dev/null +++ b/bskylink/tests/infra/with-test-db.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env sh + +# Example usage: +# ./with-test-db.sh psql postgresql://pg:password@localhost:5433/postgres -c 'select 1;' + +dir=$(dirname $0) +. ${dir}/_common.sh + +SERVICES="db_test" main "$@" diff --git a/bskylink/tsconfig.json b/bskylink/tsconfig.json new file mode 100644 index 0000000000..3c382acc41 --- /dev/null +++ b/bskylink/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "NodeNext", + "esModuleInterop": true, + "moduleResolution": "NodeNext", + "outDir": "dist", + "lib": ["ES2021.String"] + }, + "include": ["./src/index.ts", "./src/bin.ts"] + } diff --git a/bskylink/yarn.lock b/bskylink/yarn.lock new file mode 100644 index 0000000000..d2fa31456b --- /dev/null +++ b/bskylink/yarn.lock @@ -0,0 +1,1027 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@atproto/common-web@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.0.tgz#36da8c2c31d8cf8a140c3c8f03223319bf4430bb" + integrity sha512-67VnV6JJyX+ZWyjV7xFQMypAgDmjVaR9ZCuU/QW+mqlqI7fex2uL4Fv+7/jHadgzhuJHVd6OHOvNn0wR5WZYtA== + dependencies: + graphemer "^1.4.0" + multiformats "^9.9.0" + uint8arrays "3.0.0" + zod "^3.21.4" + +"@atproto/common@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.0.tgz#d77696c7eb545426df727837d9ee333b429fe7ef" + integrity sha512-yOXuPlCjT/OK9j+neIGYn9wkxx/AlxQSucysAF0xgwu0Ji8jAtKBf9Jv6R5ObYAjAD/kVUvEYumle+Yq/R9/7g== + dependencies: + "@atproto/common-web" "^0.3.0" + "@ipld/dag-cbor" "^7.0.3" + cbor-x "^1.5.1" + iso-datestring-validator "^2.2.2" + multiformats "^9.9.0" + pino "^8.15.0" + +"@cbor-extract/cbor-extract-darwin-arm64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz#8d65cb861a99622e1b4a268e2d522d2ec6137338" + integrity sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w== + +"@cbor-extract/cbor-extract-darwin-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz#9fbec199c888c5ec485a1839f4fad0485ab6c40a" + integrity sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w== + +"@cbor-extract/cbor-extract-linux-arm64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz#bf77e0db4a1d2200a5aa072e02210d5043e953ae" + integrity sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ== + +"@cbor-extract/cbor-extract-linux-arm@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz#491335037eb8533ed8e21b139c59f6df04e39709" + integrity sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q== + +"@cbor-extract/cbor-extract-linux-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz#672574485ccd24759bf8fb8eab9dbca517d35b97" + integrity sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw== + +"@cbor-extract/cbor-extract-win32-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz#4b3f07af047f984c082de34b116e765cb9af975f" + integrity sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w== + +"@ipld/dag-cbor@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz#aa31b28afb11a807c3d627828a344e5521ac4a1e" + integrity sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA== + dependencies: + cborg "^1.6.0" + multiformats "^9.5.4" + +"@types/cors@^2.8.17": + version "2.8.17" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "20.14.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.2.tgz#a5f4d2bcb4b6a87bffcaa717718c5a0f208f4a18" + integrity sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q== + dependencies: + undici-types "~5.26.4" + +"@types/pg@^8.11.6": + version "8.11.6" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.11.6.tgz#a2d0fb0a14b53951a17df5197401569fb9c0c54b" + integrity sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^4.0.1" + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +body-parser@1.20.2, body-parser@^1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +boolean@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" + integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +cbor-extract@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cbor-extract/-/cbor-extract-2.2.0.tgz#cee78e630cbeae3918d1e2e58e0cebaf3a3be840" + integrity sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA== + dependencies: + node-gyp-build-optional-packages "5.1.1" + optionalDependencies: + "@cbor-extract/cbor-extract-darwin-arm64" "2.2.0" + "@cbor-extract/cbor-extract-darwin-x64" "2.2.0" + "@cbor-extract/cbor-extract-linux-arm" "2.2.0" + "@cbor-extract/cbor-extract-linux-arm64" "2.2.0" + "@cbor-extract/cbor-extract-linux-x64" "2.2.0" + "@cbor-extract/cbor-extract-win32-x64" "2.2.0" + +cbor-x@^1.5.1: + version "1.5.9" + resolved "https://registry.yarnpkg.com/cbor-x/-/cbor-x-1.5.9.tgz#ed6b2afcd7884bdd697674bfb7332c1473a13ecf" + integrity sha512-OEI5rEu3MeR0WWNUXuIGkxmbXVhABP+VtgAXzm48c9ulkrsvxshjjk94XSOGphyAKeNGLPfAxxzEtgQ6rEVpYQ== + optionalDependencies: + cbor-extract "^2.2.0" + +cborg@^1.6.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/cborg/-/cborg-1.10.2.tgz#83cd581b55b3574c816f82696307c7512db759a1" + integrity sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +delay@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" + integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-libc@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +express@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +fast-printf@^1.6.9: + version "1.6.9" + resolved "https://registry.yarnpkg.com/fast-printf/-/fast-printf-1.6.9.tgz#212f56570d2dc8ccdd057ee93d50dd414d07d676" + integrity sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg== + dependencies: + boolean "^3.1.4" + +fast-redact@^3.1.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.5.0.tgz#e9ea02f7e57d0cd8438180083e93077e496285e4" + integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-terminator@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/http-terminator/-/http-terminator-3.2.0.tgz#bc158d2694b733ca4fbf22a35065a81a609fb3e9" + integrity sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g== + dependencies: + delay "^5.0.0" + p-wait-for "^3.2.0" + roarr "^7.0.4" + type-fest "^2.3.3" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +iso-datestring-validator@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz#2daa80d2900b7a954f9f731d42f96ee0c19a6895" + integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== + +kysely@^0.27.3: + version "0.27.3" + resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.3.tgz#6cc6c757040500b43c4ac596cdbb12be400ee276" + integrity sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multiformats@^13.0.0: + version "13.1.1" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-13.1.1.tgz#b22ce4df26330d2cf0d69f5bdcbc9a787095a6e5" + integrity sha512-JiptvwMmlxlzIlLLwhCi/srf/nk409UL0eUBr0kioRJq15hqqKyg68iftrBvhCRjR6Rw4fkNnSc4ZJXJDuta/Q== + +multiformats@^9.4.2, multiformats@^9.5.4, multiformats@^9.9.0: + version "9.9.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +node-gyp-build-optional-packages@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz#52b143b9dd77b7669073cbfe39e3f4118bfc603c" + integrity sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw== + dependencies: + detect-libc "^2.0.1" + +object-assign@^4: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +obuf@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-exit-leak-free@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" + integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-timeout@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +p-wait-for@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-wait-for/-/p-wait-for-3.2.0.tgz#640429bcabf3b0dd9f492c31539c5718cb6a3f1f" + integrity sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA== + dependencies: + p-timeout "^3.0.0" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== + +pg-connection-string@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.4.tgz#f543862adfa49fa4e14bc8a8892d2a84d754246d" + integrity sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-numeric@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a" + integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw== + +pg-pool@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.2.tgz#3a592370b8ae3f02a7c8130d245bc02fa2c5f3f2" + integrity sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg== + +pg-protocol@*, pg-protocol@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.1.tgz#21333e6d83b01faaebfe7a33a7ad6bfd9ed38cb3" + integrity sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg-types@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-4.0.2.tgz#399209a57c326f162461faa870145bb0f918b76d" + integrity sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng== + dependencies: + pg-int8 "1.0.1" + pg-numeric "1.0.2" + postgres-array "~3.0.1" + postgres-bytea "~3.0.0" + postgres-date "~2.1.0" + postgres-interval "^3.0.0" + postgres-range "^1.1.1" + +pg@^8.12.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.12.0.tgz#9341724db571022490b657908f65aee8db91df79" + integrity sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ== + dependencies: + pg-connection-string "^2.6.4" + pg-pool "^3.6.2" + pg-protocol "^1.6.1" + pg-types "^2.1.0" + pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.1" + +pgpass@1.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" + +pino-abstract-transport@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz#97f9f2631931e242da531b5c66d3079c12c9d1b5" + integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q== + dependencies: + readable-stream "^4.0.0" + split2 "^4.0.0" + +pino-std-serializers@^6.0.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz#d9a9b5f2b9a402486a5fc4db0a737570a860aab3" + integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== + +pino-std-serializers@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b" + integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== + +pino@^8.15.0: + version "8.21.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.21.0.tgz#e1207f3675a2722940d62da79a7a55a98409f00d" + integrity sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^1.2.0" + pino-std-serializers "^6.0.0" + process-warning "^3.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^3.7.0" + thread-stream "^2.6.0" + +pino@^9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-9.2.0.tgz#e77a9516f3a3e5550d9b76d9f65ac6118ef02bdd" + integrity sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^1.2.0" + pino-std-serializers "^7.0.0" + process-warning "^3.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^4.0.1" + thread-stream "^3.0.0" + +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-array@~3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" + integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-bytea@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089" + integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw== + dependencies: + obuf "~1.1.2" + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-date@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-2.1.0.tgz#b85d3c1fb6fb3c6c8db1e9942a13a3bf625189d0" + integrity sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + +postgres-interval@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a" + integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw== + +postgres-range@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.4.tgz#a59c5f9520909bcec5e63e8cf913a92e4c952863" + integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w== + +process-warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-3.0.0.tgz#96e5b88884187a1dce6f5c3166d611132058710b" + integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +readable-stream@^4.0.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" + integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + +roarr@^7.0.4: + version "7.21.1" + resolved "https://registry.yarnpkg.com/roarr/-/roarr-7.21.1.tgz#fd6452ca822a65f736c35e5372f04ee9f2ca3851" + integrity sha512-3niqt5bXFY1InKU8HKWqqYTYjtrBaxBMnXELXCXUYgtNYGUtZM5rB46HIC430AyacL95iEniGf7RgqsesykLmQ== + dependencies: + fast-printf "^1.6.9" + safe-stable-stringify "^2.4.3" + semver-compare "^1.0.0" + +safe-buffer@5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-stable-stringify@^2.3.1, safe-stable-stringify@^2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +sonic-boom@^3.7.0: + version "3.8.1" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.8.1.tgz#d5ba8c4e26d6176c9a1d14d549d9ff579a163422" + integrity sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg== + dependencies: + atomic-sleep "^1.0.0" + +sonic-boom@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.0.1.tgz#515b7cef2c9290cb362c4536388ddeece07aed30" + integrity sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ== + dependencies: + atomic-sleep "^1.0.0" + +split2@^4.0.0, split2@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +thread-stream@^2.6.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.7.0.tgz#d8a8e1b3fd538a6cca8ce69dbe5d3d097b601e11" + integrity sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw== + dependencies: + real-require "^0.2.0" + +thread-stream@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-3.1.0.tgz#4b2ef252a7c215064507d4ef70c05a5e2d34c4f1" + integrity sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A== + dependencies: + real-require "^0.2.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +type-fest@^2.3.3: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@^5.4.5: + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== + +uint8arrays@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.0.0.tgz#260869efb8422418b6f04e3fac73a3908175c63b" + integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== + dependencies: + multiformats "^9.4.2" + +uint8arrays@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-5.1.0.tgz#14047c9bdf825d025b7391299436e5e50e7270f1" + integrity sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww== + dependencies: + multiformats "^13.0.0" + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +zod@^3.21.4: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== diff --git a/bskyweb/cmd/bskyweb/main.go b/bskyweb/cmd/bskyweb/main.go index 5185ff573a..49629e3f2b 100644 --- a/bskyweb/cmd/bskyweb/main.go +++ b/bskyweb/cmd/bskyweb/main.go @@ -35,7 +35,7 @@ func run(args []string) { Flags: []cli.Flag{ &cli.StringFlag{ Name: "appview-host", - Usage: "method, hostname, and port of PDS instance", + Usage: "scheme, hostname, and port of PDS instance", Value: "http://localhost:2584", // retain old PDS env var for easy transition EnvVars: []string{"ATP_APPVIEW_HOST", "ATP_PDS_HOST"}, @@ -47,6 +47,13 @@ func run(args []string) { Value: ":8100", EnvVars: []string{"HTTP_ADDRESS"}, }, + &cli.StringFlag{ + Name: "link-host", + Usage: "scheme, hostname, and port of link service", + Required: false, + Value: "", + EnvVars: []string{"LINK_HOST"}, + }, &cli.BoolFlag{ Name: "debug", Usage: "Enable debug mode", diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index bb81e780f5..6d32e0e212 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -6,6 +6,7 @@ import ( "fmt" "io/fs" "net/http" + "net/url" "os" "os/signal" "strings" @@ -36,6 +37,7 @@ func serve(cctx *cli.Context) error { debug := cctx.Bool("debug") httpAddress := cctx.String("http-address") appviewHost := cctx.String("appview-host") + linkHost := cctx.String("link-host") // Echo e := echo.New() @@ -221,6 +223,14 @@ func serve(cctx *cli.Context) error { e.GET("/profile/:handleOrDID/post/:rkey/liked-by", server.WebGeneric) e.GET("/profile/:handleOrDID/post/:rkey/reposted-by", server.WebGeneric) + if linkHost != "" { + linkUrl, err := url.Parse(linkHost) + if err != nil { + return err + } + e.Group("/:linkId", server.LinkProxyMiddleware(linkUrl)) + } + // Start the server. log.Infof("starting server address=%s", httpAddress) go func() { @@ -292,6 +302,30 @@ func (srv *Server) Download(c echo.Context) error { return c.Redirect(http.StatusFound, "/") } +// Handler for proxying top-level paths to link service, which ends up serving a redirect +func (srv *Server) LinkProxyMiddleware(url *url.URL) echo.MiddlewareFunc { + return middleware.ProxyWithConfig( + middleware.ProxyConfig{ + Balancer: middleware.NewRoundRobinBalancer( + []*middleware.ProxyTarget{{URL: url}}, + ), + Skipper: func(c echo.Context) bool { + req := c.Request() + if req.Method == "GET" && + strings.LastIndex(strings.TrimRight(req.URL.Path, "/"), "/") == 0 && // top-level path + !strings.HasPrefix(req.URL.Path, "/_") { // e.g. /_health endpoint + return false + } + return true + }, + RetryCount: 2, + ErrorHandler: func(c echo.Context, err error) error { + return c.Redirect(302, "/") + }, + }, + ) +} + // handler for endpoint that have no specific server-side handling func (srv *Server) WebGeneric(c echo.Context) error { data := pongo2.Context{} From cb376479493dbc3a24876449f6466789ddcef6ea Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 21 Jun 2024 14:50:49 -0500 Subject: [PATCH 085/127] Fetch more than 3 suggested follows after first load (#4595) * Fetch more than 3 sugg follows after first load * Preview handling via overfetching --- src/state/queries/suggested-follows.ts | 9 +++++++-- src/view/screens/Search/Explore.tsx | 25 +++++++++++++++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts index 40251d43d7..a1244721a2 100644 --- a/src/state/queries/suggested-follows.ts +++ b/src/state/queries/suggested-follows.ts @@ -34,13 +34,14 @@ const suggestedFollowsByActorQueryKey = (did: string) => [ did, ] -type SuggestedFollowsOptions = {limit?: number} +type SuggestedFollowsOptions = {limit?: number; subsequentPageLimit?: number} export function useSuggestedFollowsQuery(options?: SuggestedFollowsOptions) { const {currentAccount} = useSession() const agent = useAgent() const moderationOpts = useModerationOpts() const {data: preferences} = usePreferencesQuery() + const limit = options?.limit || 25 return useInfiniteQuery< AppBskyActorGetSuggestions.OutputSchema, @@ -54,9 +55,13 @@ export function useSuggestedFollowsQuery(options?: SuggestedFollowsOptions) { queryKey: suggestedFollowsQueryKey(options), queryFn: async ({pageParam}) => { const contentLangs = getContentLanguages().join(',') + const maybeDifferentLimit = + options?.subsequentPageLimit && pageParam + ? options.subsequentPageLimit + : limit const res = await agent.app.bsky.actor.getSuggestions( { - limit: options?.limit || 25, + limit: maybeDifferentLimit, cursor: pageParam, }, { diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index dd93bf8130..f6988548b2 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -282,7 +282,7 @@ export function Explore() { isFetchingNextPage: isFetchingNextProfilesPage, error: profilesError, fetchNextPage: fetchNextProfilesPage, - } = useSuggestedFollowsQuery({limit: 3}) + } = useSuggestedFollowsQuery({limit: 6, subsequentPageLimit: 10}) const { data: feeds, hasNextPage: hasNextFeedsPage, @@ -290,7 +290,7 @@ export function Explore() { isFetchingNextPage: isFetchingNextFeedsPage, error: feedsError, fetchNextPage: fetchNextFeedsPage, - } = useGetPopularFeedsQuery({limit: 3}) + } = useGetPopularFeedsQuery({limit: 10}) const isLoadingMoreProfiles = isFetchingNextProfilesPage && !isLoadingProfiles const onLoadMoreProfiles = React.useCallback(async () => { @@ -340,11 +340,12 @@ export function Explore() { // Currently the responses contain duplicate items. // Needs to be fixed on backend, but let's dedupe to be safe. let seen = new Set() + const profileItems: ExploreScreenItems[] = [] for (const page of profiles.pages) { for (const actor of page.actors) { if (!seen.has(actor.did)) { seen.add(actor.did) - i.push({ + profileItems.push({ type: 'profile', key: actor.did, profile: actor, @@ -354,13 +355,19 @@ export function Explore() { } if (hasNextProfilesPage) { + // splice off 3 as previews if we have a next page + const previews = profileItems.splice(-3) + // push remainder + i.push(...profileItems) i.push({ type: 'loadMore', key: 'loadMoreProfiles', isLoadingMore: isLoadingMoreProfiles, onLoadMore: onLoadMoreProfiles, - items: i.filter(item => item.type === 'profile').slice(-3), + items: previews, }) + } else { + i.push(...profileItems) } } else { if (profilesError) { @@ -390,11 +397,12 @@ export function Explore() { // Currently the responses contain duplicate items. // Needs to be fixed on backend, but let's dedupe to be safe. let seen = new Set() + const feedItems: ExploreScreenItems[] = [] for (const page of feeds.pages) { for (const feed of page.feeds) { if (!seen.has(feed.uri)) { seen.add(feed.uri) - i.push({ + feedItems.push({ type: 'feed', key: feed.uri, feed, @@ -403,6 +411,7 @@ export function Explore() { } } + // feeds errors can occur during pagination, so feeds is truthy if (feedsError) { i.push({ type: 'error', @@ -418,13 +427,17 @@ export function Explore() { error: cleanError(preferencesError), }) } else if (hasNextFeedsPage) { + const preview = feedItems.splice(-3) + i.push(...feedItems) i.push({ type: 'loadMore', key: 'loadMoreFeeds', isLoadingMore: isLoadingMoreFeeds, onLoadMore: onLoadMoreFeeds, - items: i.filter(item => item.type === 'feed').slice(-3), + items: preview, }) + } else { + i.push(...feedItems) } } else { if (feedsError) { From 4d6787009ccbae2812aaeddefe6dc77742363f36 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 21 Jun 2024 16:50:23 -0500 Subject: [PATCH 086/127] Pinned feeds cards (#4526) * Add lists support to FeedCard * Add useSavedFeeds query, similar to usePinnedFeedInfos * Integrate into Feeds screen * Fix alignment on mobile * Update usages * Add placeholder loading state * Handle no feeds state * Reuse previous data for placeholder * Staged loading * Improve staged loading * Use setQueryData approach to pre-caching * Add types for a little more safety * Fix precaching --------- Co-authored-by: Dan Abramov --- src/components/FeedCard.tsx | 135 ++++++++-- src/state/queries/feed.ts | 139 ++++++++++ src/state/queries/resolve-uri.ts | 15 +- src/view/screens/Feeds.tsx | 387 +++++++++++++--------------- src/view/screens/Search/Explore.tsx | 2 +- src/view/screens/Search/Search.tsx | 2 +- 6 files changed, 447 insertions(+), 233 deletions(-) diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx index 94d97cb620..bd0649097e 100644 --- a/src/components/FeedCard.tsx +++ b/src/components/FeedCard.tsx @@ -1,6 +1,11 @@ import React from 'react' import {GestureResponderEvent, View} from 'react-native' -import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api' +import { + AppBskyActorDefs, + AppBskyFeedDefs, + AppBskyGraphDefs, + AtUri, +} from '@atproto/api' import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -20,23 +25,35 @@ import {Button, ButtonIcon} from '#/components/Button' import {useRichText} from '#/components/hooks/useRichText' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' -import {Link as InternalLink} from '#/components/Link' +import {Link as InternalLink, LinkProps} from '#/components/Link' import {Loader} from '#/components/Loader' import * as Prompt from '#/components/Prompt' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' -export function Default({feed}: {feed: AppBskyFeedDefs.GeneratorView}) { +export function Default({ + type, + view, +}: + | { + type: 'feed' + view: AppBskyFeedDefs.GeneratorView + } + | { + type: 'list' + view: AppBskyGraphDefs.ListView + }) { + const displayName = type === 'feed' ? view.displayName : view.name return ( - +
- - - + + +
- - + + {type === 'feed' && }
) @@ -46,13 +63,10 @@ export function Link({ children, feed, }: { - children: React.ReactElement - feed: AppBskyFeedDefs.GeneratorView -}) { + feed: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView +} & Omit) { const href = React.useMemo(() => { - const urip = new AtUri(feed.uri) - const handleOrDid = feed.creator.handle || feed.creator.did - return `/profile/${handleOrDid}/feed/${urip.rkey}` + return createProfileFeedHref({feed}) }, [feed]) return {children} } @@ -62,11 +76,33 @@ export function Outer({children}: {children: React.ReactNode}) { } export function Header({children}: {children: React.ReactNode}) { - return {children} + return ( + + {children} + + ) } -export function Avatar({src}: {src: string | undefined}) { - return +export type AvatarProps = {src: string | undefined; size?: number} + +export function Avatar({src, size = 40}: AvatarProps) { + return +} + +export function AvatarPlaceholder({size = 40}: Omit) { + const t = useTheme() + return ( + + ) } export function TitleAndByline({ @@ -74,22 +110,54 @@ export function TitleAndByline({ creator, }: { title: string - creator: AppBskyActorDefs.ProfileViewBasic + creator?: AppBskyActorDefs.ProfileViewBasic }) { const t = useTheme() return ( - + {title} - - Feed by {sanitizeHandle(creator.handle, '@')} - + {creator && ( + + Feed by {sanitizeHandle(creator.handle, '@')} + + )} + + ) +} + +export function TitleAndBylinePlaceholder({creator}: {creator?: boolean}) { + const t = useTheme() + + return ( + + + + {creator && ( + + )} ) } @@ -203,3 +271,16 @@ function ActionInner({uri, pin}: {uri: string; pin?: boolean}) { ) } + +export function createProfileFeedHref({ + feed, +}: { + feed: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView +}) { + const urip = new AtUri(feed.uri) + const type = urip.collection === 'app.bsky.feed.generator' ? 'feed' : 'list' + const handleOrDid = feed.creator.handle || feed.creator.did + return `/profile/${handleOrDid}/${type === 'feed' ? 'feed' : 'lists'}/${ + urip.rkey + }` +} diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index 83d6a7634d..972dbf995a 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -9,20 +9,24 @@ import { } from '@atproto/api' import { InfiniteData, + QueryClient, QueryKey, useInfiniteQuery, useMutation, useQuery, + useQueryClient, } from '@tanstack/react-query' import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {STALE} from '#/state/queries' +import {RQKEY as listQueryKey} from '#/state/queries/list' import {usePreferencesQuery} from '#/state/queries/preferences' import {useAgent, useSession} from '#/state/session' import {router} from '#/routes' import {FeedDescriptor} from './post-feed' +import {precacheResolvedUri} from './resolve-uri' export type FeedSourceFeedInfo = { type: 'feed' @@ -201,6 +205,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { const agent = useAgent() const limit = options?.limit || 10 const {data: preferences} = usePreferencesQuery() + const queryClient = useQueryClient() // Make sure this doesn't invalidate unless really needed. const selectArgs = useMemo( @@ -225,6 +230,13 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { limit, cursor: pageParam, }) + + // precache feeds + for (const feed of res.data.feeds) { + const hydratedFeed = hydrateFeedGenerator(feed) + precacheFeed(queryClient, hydratedFeed) + } + return res.data }, initialPageParam: undefined, @@ -449,3 +461,130 @@ export function usePinnedFeedsInfos() { }, }) } + +export type SavedFeedItem = + | { + type: 'feed' + config: AppBskyActorDefs.SavedFeed + view: AppBskyFeedDefs.GeneratorView + } + | { + type: 'list' + config: AppBskyActorDefs.SavedFeed + view: AppBskyGraphDefs.ListView + } + | { + type: 'timeline' + config: AppBskyActorDefs.SavedFeed + view: undefined + } + +export function useSavedFeeds() { + const agent = useAgent() + const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery() + const savedItems = preferences?.savedFeeds ?? [] + const queryClient = useQueryClient() + + return useQuery({ + staleTime: STALE.INFINITY, + enabled: !isLoadingPrefs, + queryKey: [pinnedFeedInfosQueryKeyRoot, ...savedItems], + placeholderData: previousData => { + return ( + previousData || { + count: savedItems.length, + feeds: [], + } + ) + }, + queryFn: async () => { + const resolvedFeeds = new Map() + const resolvedLists = new Map() + + const savedFeeds = savedItems.filter(feed => feed.type === 'feed') + const savedLists = savedItems.filter(feed => feed.type === 'list') + + let feedsPromise = Promise.resolve() + if (savedFeeds.length > 0) { + feedsPromise = agent.app.bsky.feed + .getFeedGenerators({ + feeds: savedFeeds.map(f => f.value), + }) + .then(res => { + res.data.feeds.forEach(f => { + resolvedFeeds.set(f.uri, f) + }) + }) + } + + const listsPromises = savedLists.map(list => + agent.app.bsky.graph + .getList({ + list: list.value, + limit: 1, + }) + .then(res => { + const listView = res.data.list + resolvedLists.set(listView.uri, listView) + }), + ) + + await Promise.allSettled([feedsPromise, ...listsPromises]) + + resolvedFeeds.forEach(feed => { + const hydratedFeed = hydrateFeedGenerator(feed) + precacheFeed(queryClient, hydratedFeed) + }) + resolvedLists.forEach(list => { + precacheList(queryClient, list) + }) + + const res: SavedFeedItem[] = savedItems.map(s => { + if (s.type === 'timeline') { + return { + type: 'timeline', + config: s, + view: undefined, + } + } + + return { + type: s.type, + config: s, + view: + s.type === 'feed' + ? resolvedFeeds.get(s.value) + : resolvedLists.get(s.value), + } + }) as SavedFeedItem[] + + return { + count: savedItems.length, + feeds: res, + } + }, + }) +} + +function precacheFeed(queryClient: QueryClient, hydratedFeed: FeedSourceInfo) { + precacheResolvedUri( + queryClient, + hydratedFeed.creatorHandle, + hydratedFeed.creatorDid, + ) + queryClient.setQueryData( + feedSourceInfoQueryKey({uri: hydratedFeed.uri}), + hydratedFeed, + ) +} + +function precacheList( + queryClient: QueryClient, + list: AppBskyGraphDefs.ListView, +) { + precacheResolvedUri(queryClient, list.creator.handle, list.creator.did) + queryClient.setQueryData( + listQueryKey(list.uri), + list, + ) +} diff --git a/src/state/queries/resolve-uri.ts b/src/state/queries/resolve-uri.ts index 7bd26435cf..c1fd8e240a 100644 --- a/src/state/queries/resolve-uri.ts +++ b/src/state/queries/resolve-uri.ts @@ -1,5 +1,10 @@ import {AppBskyActorDefs, AtUri} from '@atproto/api' -import {useQuery, useQueryClient, UseQueryResult} from '@tanstack/react-query' +import { + QueryClient, + useQuery, + useQueryClient, + UseQueryResult, +} from '@tanstack/react-query' import {STALE} from '#/state/queries' import {useAgent} from '#/state/session' @@ -50,3 +55,11 @@ export function useResolveDidQuery(didOrHandle: string | undefined) { enabled: !!didOrHandle, }) } + +export function precacheResolvedUri( + queryClient: QueryClient, + handle: string, + did: string, +) { + queryClient.setQueryData(RQKEY(handle), did) +} diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index 1345211775..70437a9e76 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -1,8 +1,6 @@ import React from 'react' import {ActivityIndicator, type FlatList, StyleSheet, View} from 'react-native' -import {AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' +import {AppBskyFeedDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' @@ -10,12 +8,11 @@ import debounce from 'lodash.debounce' import {isNative, isWeb} from '#/platform/detection' import { - getAvatarTypeFromUri, - useFeedSourceInfoQuery, + SavedFeedItem, useGetPopularFeedsQuery, + useSavedFeeds, useSearchPopularFeedsMutation, } from '#/state/queries/feed' -import {usePreferencesQuery} from '#/state/queries/preferences' import {useSession} from '#/state/session' import {useSetMinimalShellMode} from '#/state/shell' import {useComposerControls} from '#/state/shell/composer' @@ -28,14 +25,10 @@ import {s} from 'lib/styles' import {ErrorMessage} from 'view/com/util/error/ErrorMessage' import {FAB} from 'view/com/util/fab/FAB' import {SearchInput} from 'view/com/util/forms/SearchInput' -import {Link, TextLink} from 'view/com/util/Link' +import {TextLink} from 'view/com/util/Link' import {List} from 'view/com/util/List' -import { - FeedFeedLoadingPlaceholder, - LoadingPlaceholder, -} from 'view/com/util/LoadingPlaceholder' +import {FeedFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' import {Text} from 'view/com/util/text/Text' -import {UserAvatar} from 'view/com/util/UserAvatar' import {ViewHeader} from 'view/com/util/ViewHeader' import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed' import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType' @@ -47,6 +40,7 @@ import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkl import hairlineWidth = StyleSheet.hairlineWidth import {Divider} from '#/components/Divider' import * as FeedCard from '#/components/FeedCard' +import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' type Props = NativeStackScreenProps @@ -61,9 +55,8 @@ type FlatlistSlice = key: string } | { - type: 'savedFeedsLoading' + type: 'savedFeedPlaceholder' key: string - // pendingItems: number, } | { type: 'savedFeedNoResults' @@ -72,8 +65,7 @@ type FlatlistSlice = | { type: 'savedFeed' key: string - feedUri: string - savedFeedConfig: AppBskyActorDefs.SavedFeed + savedFeed: SavedFeedItem } | { type: 'savedFeedsLoadMore' @@ -113,11 +105,11 @@ export function FeedsScreen(_props: Props) { const [query, setQuery] = React.useState('') const [isPTR, setIsPTR] = React.useState(false) const { - data: preferences, - isLoading: isPreferencesLoading, - error: preferencesError, - refetch: refetchPreferences, - } = usePreferencesQuery() + data: savedFeeds, + isPlaceholderData: isSavedFeedsPlaceholder, + error: savedFeedsError, + refetch: refetchSavedFeeds, + } = useSavedFeeds() const { data: popularFeeds, isFetching: isPopularFeedsFetching, @@ -173,11 +165,11 @@ export function FeedsScreen(_props: Props) { const onPullToRefresh = React.useCallback(async () => { setIsPTR(true) await Promise.all([ - refetchPreferences().catch(_e => undefined), + refetchSavedFeeds().catch(_e => undefined), refetchPopularFeeds().catch(_e => undefined), ]) setIsPTR(false) - }, [setIsPTR, refetchPreferences, refetchPopularFeeds]) + }, [setIsPTR, refetchSavedFeeds, refetchPopularFeeds]) const onEndReached = React.useCallback(() => { if ( isPopularFeedsFetching || @@ -203,6 +195,11 @@ export function FeedsScreen(_props: Props) { const items = React.useMemo(() => { let slices: FlatlistSlice[] = [] + const hasActualSavedCount = + !isSavedFeedsPlaceholder || + (isSavedFeedsPlaceholder && (savedFeeds?.count || 0) > 0) + const canShowDiscoverSection = + !hasSession || (hasSession && hasActualSavedCount) if (hasSession) { slices.push({ @@ -210,47 +207,63 @@ export function FeedsScreen(_props: Props) { type: 'savedFeedsHeader', }) - if (preferencesError) { + if (savedFeedsError) { slices.push({ key: 'savedFeedsError', type: 'error', - error: cleanError(preferencesError.toString()), + error: cleanError(savedFeedsError.toString()), }) } else { - if (isPreferencesLoading || !preferences?.savedFeeds) { - slices.push({ - key: 'savedFeedsLoading', - type: 'savedFeedsLoading', - // pendingItems: this.rootStore.preferences.savedFeeds.length || 3, - }) + if (isSavedFeedsPlaceholder && !savedFeeds?.feeds.length) { + /* + * Initial render in placeholder state is 0 on a cold page load, + * because preferences haven't loaded yet. + * + * In practice, `savedFeeds` is always defined, but we check for TS + * and for safety. + * + * In both cases, we show 4 as the the loading state. + */ + const min = 8 + const count = savedFeeds + ? savedFeeds.count === 0 + ? min + : savedFeeds.count + : min + Array(count) + .fill(0) + .forEach((_, i) => { + slices.push({ + key: 'savedFeedPlaceholder' + i, + type: 'savedFeedPlaceholder', + }) + }) } else { - if (preferences.savedFeeds?.length) { - const noFollowingFeed = preferences.savedFeeds.every( + if (savedFeeds?.feeds?.length) { + const noFollowingFeed = savedFeeds.feeds.every( f => f.type !== 'timeline', ) slices = slices.concat( - preferences.savedFeeds - .filter(f => { - return f.pinned + savedFeeds.feeds + .filter(s => { + return s.config.pinned }) - .map(feed => ({ - key: `savedFeed:${feed.value}:${feed.id}`, + .map(s => ({ + key: `savedFeed:${s.view?.uri}:${s.config.id}`, type: 'savedFeed', - feedUri: feed.value, - savedFeedConfig: feed, + savedFeed: s, })), ) slices = slices.concat( - preferences.savedFeeds - .filter(f => { - return !f.pinned + savedFeeds.feeds + .filter(s => { + return !s.config.pinned }) - .map(feed => ({ - key: `savedFeed:${feed.value}:${feed.id}`, + .map(s => ({ + key: `savedFeed:${s.view?.uri}:${s.config.id}`, type: 'savedFeed', - feedUri: feed.value, - savedFeedConfig: feed, + savedFeed: s, })), ) @@ -270,59 +283,36 @@ export function FeedsScreen(_props: Props) { } } - slices.push({ - key: 'popularFeedsHeader', - type: 'popularFeedsHeader', - }) - - if (popularFeedsError || searchError) { + if (!hasSession || (hasSession && canShowDiscoverSection)) { slices.push({ - key: 'popularFeedsError', - type: 'error', - error: cleanError( - popularFeedsError?.toString() ?? searchError?.toString() ?? '', - ), + key: 'popularFeedsHeader', + type: 'popularFeedsHeader', }) - } else { - if (isUserSearching) { - if (isSearchPending || !searchResults) { - slices.push({ - key: 'popularFeedsLoading', - type: 'popularFeedsLoading', - }) - } else { - if (!searchResults || searchResults?.length === 0) { - slices.push({ - key: 'popularFeedsNoResults', - type: 'popularFeedsNoResults', - }) - } else { - slices = slices.concat( - searchResults.map(feed => ({ - key: `popularFeed:${feed.uri}`, - type: 'popularFeed', - feedUri: feed.uri, - feed, - })), - ) - } - } + + if (popularFeedsError || searchError) { + slices.push({ + key: 'popularFeedsError', + type: 'error', + error: cleanError( + popularFeedsError?.toString() ?? searchError?.toString() ?? '', + ), + }) } else { - if (isPopularFeedsFetching && !popularFeeds?.pages) { - slices.push({ - key: 'popularFeedsLoading', - type: 'popularFeedsLoading', - }) - } else { - if (!popularFeeds?.pages) { + if (isUserSearching) { + if (isSearchPending || !searchResults) { slices.push({ - key: 'popularFeedsNoResults', - type: 'popularFeedsNoResults', + key: 'popularFeedsLoading', + type: 'popularFeedsLoading', }) } else { - for (const page of popularFeeds.pages || []) { + if (!searchResults || searchResults?.length === 0) { + slices.push({ + key: 'popularFeedsNoResults', + type: 'popularFeedsNoResults', + }) + } else { slices = slices.concat( - page.feeds.map(feed => ({ + searchResults.map(feed => ({ key: `popularFeed:${feed.uri}`, type: 'popularFeed', feedUri: feed.uri, @@ -330,12 +320,37 @@ export function FeedsScreen(_props: Props) { })), ) } - - if (isPopularFeedsFetchingNextPage) { + } + } else { + if (isPopularFeedsFetching && !popularFeeds?.pages) { + slices.push({ + key: 'popularFeedsLoading', + type: 'popularFeedsLoading', + }) + } else { + if (!popularFeeds?.pages) { slices.push({ - key: 'popularFeedsLoadingMore', - type: 'popularFeedsLoadingMore', + key: 'popularFeedsNoResults', + type: 'popularFeedsNoResults', }) + } else { + for (const page of popularFeeds.pages || []) { + slices = slices.concat( + page.feeds.map(feed => ({ + key: `popularFeed:${feed.uri}`, + type: 'popularFeed', + feedUri: feed.uri, + feed, + })), + ) + } + + if (isPopularFeedsFetchingNextPage) { + slices.push({ + key: 'popularFeedsLoadingMore', + type: 'popularFeedsLoadingMore', + }) + } } } } @@ -345,9 +360,9 @@ export function FeedsScreen(_props: Props) { return slices }, [ hasSession, - preferences, - isPreferencesLoading, - preferencesError, + savedFeeds, + isSavedFeedsPlaceholder, + savedFeedsError, popularFeeds, isPopularFeedsFetching, popularFeedsError, @@ -407,10 +422,7 @@ export function FeedsScreen(_props: Props) { ({item}: {item: FlatlistSlice}) => { if (item.type === 'error') { return - } else if ( - item.type === 'popularFeedsLoadingMore' || - item.type === 'savedFeedsLoading' - ) { + } else if (item.type === 'popularFeedsLoadingMore') { return ( @@ -459,8 +471,10 @@ export function FeedsScreen(_props: Props) { ) + } else if (item.type === 'savedFeedPlaceholder') { + return } else if (item.type === 'savedFeed') { - return + return } else if (item.type === 'popularFeedsHeader') { return ( <> @@ -481,7 +495,7 @@ export function FeedsScreen(_props: Props) { } else if (item.type === 'popularFeed') { return ( - + ) @@ -571,136 +585,103 @@ export function FeedsScreen(_props: Props) { ) } -function FeedOrFollowing({ - savedFeedConfig: feed, -}: { - savedFeedConfig: AppBskyActorDefs.SavedFeed -}) { - return feed.type === 'timeline' ? ( +function FeedOrFollowing({savedFeed}: {savedFeed: SavedFeedItem}) { + return savedFeed.type === 'timeline' ? ( ) : ( - + ) } function FollowingFeed() { - const pal = usePalette('default') const t = useTheme() - const {isMobile} = useWebMediaQueries() + const {_} = useLingui() return ( - - + - - - - Following - - + ]}> + + + + ) } function SavedFeed({ - savedFeedConfig: feed, + savedFeed, }: { - savedFeedConfig: AppBskyActorDefs.SavedFeed + savedFeed: SavedFeedItem & {type: 'feed' | 'list'} }) { - const pal = usePalette('default') - const {isMobile} = useWebMediaQueries() - const {data: info, error} = useFeedSourceInfoQuery({uri: feed.value}) - const typeAvatar = getAvatarTypeFromUri(feed.value) - - if (!info) - return ( - - ) + const t = useTheme() + const {view: feed} = savedFeed + const displayName = + savedFeed.type === 'feed' ? savedFeed.view.displayName : savedFeed.view.name return ( - - {error ? ( + + {({hovered, pressed}) => ( - - - ) : ( - - )} - - - {info.displayName} - - {error ? ( - - - Feed offline - - - ) : null} - + style={[ + a.flex_1, + a.px_lg, + a.py_md, + a.border_b, + t.atoms.border_contrast_low, + (hovered || pressed) && t.atoms.bg_contrast_25, + ]}> + + + - {isMobile && ( - + + + )} - + ) } -function SavedFeedLoadingPlaceholder() { - const pal = usePalette('default') - const {isMobile} = useWebMediaQueries() +function SavedFeedPlaceholder() { + const t = useTheme() return ( - - + + + + ) } diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index f6988548b2..8f6f6d4ba7 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -505,7 +505,7 @@ export function Explore() { a.px_lg, a.py_lg, ]}> - + ) } diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index 0b1fe37aaf..76ffba935f 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -306,7 +306,7 @@ let SearchScreenFeedsResults = ({ a.px_lg, a.py_lg, ]}> - + )} keyExtractor={item => item.uri} From 4d9e686e3b77756f8a404c4b38c7016f84a75ea0 Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 21 Jun 2024 15:15:12 -0700 Subject: [PATCH 087/127] add flex shrink (#4597) --- src/components/ProfileHoverCard/index.web.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index 319eccfa4a..4db9c4f8e5 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -64,7 +64,7 @@ export function ProfileHoverCard(props: ProfileHoverCardProps) { return props.children } else { return ( - + ) From 707ea5bf062755d4fd3a9476a4457cdc9d4991b7 Mon Sep 17 00:00:00 2001 From: Minseo Lee Date: Sat, 22 Jun 2024 07:41:58 +0900 Subject: [PATCH 088/127] Add options for Feeds in `Navigation.tsx` (#4503) * Update Navigation.tsx * Update Navigation.tsx --- src/Navigation.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 5d4ba0e3f7..f2b7cd911f 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -312,7 +312,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { getComponent={() => MessagesSettingsScreen} options={{title: title(msg`Chat settings`), requireAuth: true}} /> - FeedsScreen} /> + FeedsScreen} + options={{title: title(msg`Feeds`)}} + /> ) } From 7db8dd8980c71e189315d89289196820db8b7875 Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 22 Jun 2024 02:11:39 +0300 Subject: [PATCH 089/127] Add debug feedContext label (#4598) --- src/lib/statsig/gates.ts | 1 + src/view/com/util/post-ctrls/PostCtrls.tsx | 29 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 6e460dc60f..46ef934ef6 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -1,5 +1,6 @@ export type Gate = // Keep this alphabetic please. + | 'debug_show_feedcontext' | 'native_pwi_disabled' | 'request_notifications_permission_after_onboarding_v2' | 'show_avi_follow_button' diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 472ce4043a..231808bf28 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -6,6 +6,7 @@ import { View, type ViewStyle, } from 'react-native' +import * as Clipboard from 'expo-clipboard' import { AppBskyFeedDefs, AppBskyFeedPost, @@ -19,6 +20,7 @@ import {POST_CTRL_HITSLOP} from '#/lib/constants' import {useHaptics} from '#/lib/haptics' import {makeProfileLink} from '#/lib/routes/links' import {shareUrl} from '#/lib/sharing' +import {useGate} from '#/lib/statsig/statsig' import {toShareUrl} from '#/lib/strings/url-helpers' import {s} from '#/lib/styles' import {Shadow} from '#/state/cache/types' @@ -41,6 +43,7 @@ import * as Prompt from '#/components/Prompt' import {PostDropdownBtn} from '../forms/PostDropdownBtn' import {formatCount} from '../numeric/format' import {Text} from '../text/Text' +import * as Toast from '../Toast' import {RepostButton} from './RepostButton' let PostCtrls = ({ @@ -75,6 +78,7 @@ let PostCtrls = ({ const loggedOutWarningPromptControl = useDialogControl() const {sendInteraction} = useFeedFeedbackContext() const playHaptic = useHaptics() + const gate = useGate() const shouldShowLoggedOutWarning = React.useMemo(() => { return ( @@ -329,6 +333,31 @@ let PostCtrls = ({ timestamp={post.indexedAt} /> + {gate('debug_show_feedcontext') && feedContext && ( + { + e.stopPropagation() + Clipboard.setStringAsync(feedContext) + Toast.show(_(msg`Copied to clipboard`)) + }}> + + {feedContext} + + + )} ) } From 1715afd80ed7d9de1f2d82befa04815015d34a3a Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 22 Jun 2024 03:54:47 +0300 Subject: [PATCH 090/127] [Statsig] Send Discover aggregate interactions (#4599) --- src/lib/statsig/events.ts | 16 ++++++ src/lib/statsig/statsig.tsx | 3 + src/state/feed-feedback.tsx | 107 +++++++++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index 0d77ec8a36..2e8cedb54b 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -73,6 +73,22 @@ export type LogEvents = { feedType: string reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest' } + 'discover:showMore': { + feedContext: string + } + 'discover:showLess': { + feedContext: string + } + 'discover:clickthrough:sampled': { + count: number + } + 'discover:engaged:sampled': { + count: number + } + 'discover:seen:sampled': { + count: number + } + 'composer:gif:open': {} 'composer:gif:select': {} diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index b5a239c3a5..94a1e63d0e 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -115,6 +115,9 @@ const DOWNSAMPLED_EVENTS: Set = new Set([ 'home:feedDisplayed:sampled', 'feed:endReached:sampled', 'feed:refresh:sampled', + 'discover:clickthrough:sampled', + 'discover:engaged:sampled', + 'discover:seen:sampled', ]) const isDownsampledSession = Math.random() < 0.9 // 90% likely diff --git a/src/state/feed-feedback.tsx b/src/state/feed-feedback.tsx index 64bdd4b893..88f50daca4 100644 --- a/src/state/feed-feedback.tsx +++ b/src/state/feed-feedback.tsx @@ -4,6 +4,7 @@ import {AppBskyFeedDefs, BskyAgent} from '@atproto/api' import throttle from 'lodash.throttle' import {PROD_DEFAULT_FEED} from '#/lib/constants' +import {logEvent} from '#/lib/statsig/statsig' import {logger} from '#/logger' import { FeedDescriptor, @@ -34,6 +35,16 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) { WeakSet >(new WeakSet()) + const aggregatedStats = React.useRef(null) + const throttledFlushAggregatedStats = React.useMemo( + () => + throttle(() => flushToStatsig(aggregatedStats.current), 45e3, { + leading: true, // The outer call is already throttled somewhat. + trailing: true, + }), + [], + ) + const sendToFeedNoDelay = React.useCallback(() => { const proxyAgent = agent.withProxy( // @ts-ignore TODO need to update withProxy() to support this key -prf @@ -45,12 +56,20 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) { const interactions = Array.from(queue.current).map(toInteraction) queue.current.clear() + // Send to the feed proxyAgent.app.bsky.feed .sendInteractions({interactions}) .catch((e: any) => { logger.warn('Failed to send feed interactions', {error: e}) }) - }, [agent]) + + // Send to Statsig + if (aggregatedStats.current === null) { + aggregatedStats.current = createAggregatedStats() + } + sendOrAggregateInteractionsForStats(aggregatedStats.current, interactions) + throttledFlushAggregatedStats() + }, [agent, throttledFlushAggregatedStats]) const sendToFeed = React.useMemo( () => @@ -149,3 +168,89 @@ function toInteraction(str: string): AppBskyFeedDefs.Interaction { const [item, event, feedContext] = str.split('|') return {item, event, feedContext} } + +type AggregatedStats = { + clickthroughCount: number + engagedCount: number + seenCount: number +} + +function createAggregatedStats(): AggregatedStats { + return { + clickthroughCount: 0, + engagedCount: 0, + seenCount: 0, + } +} + +function sendOrAggregateInteractionsForStats( + stats: AggregatedStats, + interactions: AppBskyFeedDefs.Interaction[], +) { + for (let interaction of interactions) { + switch (interaction.event) { + // Pressing "Show more" / "Show less" is relatively uncommon so we won't aggregate them. + // This lets us send the feed context together with them. + case 'app.bsky.feed.defs#requestLess': { + logEvent('discover:showLess', { + feedContext: interaction.feedContext ?? '', + }) + break + } + case 'app.bsky.feed.defs#requestMore': { + logEvent('discover:showMore', { + feedContext: interaction.feedContext ?? '', + }) + break + } + + // The rest of the events are aggregated and sent later in batches. + case 'app.bsky.feed.defs#clickthroughAuthor': + case 'app.bsky.feed.defs#clickthroughEmbed': + case 'app.bsky.feed.defs#clickthroughItem': + case 'app.bsky.feed.defs#clickthroughReposter': { + stats.clickthroughCount++ + break + } + case 'app.bsky.feed.defs#interactionLike': + case 'app.bsky.feed.defs#interactionQuote': + case 'app.bsky.feed.defs#interactionReply': + case 'app.bsky.feed.defs#interactionRepost': + case 'app.bsky.feed.defs#interactionShare': { + stats.engagedCount++ + break + } + case 'app.bsky.feed.defs#interactionSeen': { + stats.seenCount++ + break + } + } + } +} + +function flushToStatsig(stats: AggregatedStats | null) { + if (stats === null) { + return + } + + if (stats.clickthroughCount > 0) { + logEvent('discover:clickthrough:sampled', { + count: stats.clickthroughCount, + }) + stats.clickthroughCount = 0 + } + + if (stats.engagedCount > 0) { + logEvent('discover:engaged:sampled', { + count: stats.engagedCount, + }) + stats.engagedCount = 0 + } + + if (stats.seenCount > 0) { + logEvent('discover:seen:sampled', { + count: stats.seenCount, + }) + stats.seenCount = 0 + } +} From 35f64535cb8dfa0fe46e740a6398f3b991ecfbc7 Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 21 Jun 2024 19:59:08 -0700 Subject: [PATCH 091/127] Tweak feed card to prevent spinnerz when pushing to screen (#4600) --- src/components/FeedCard.tsx | 102 ++++++++++++++++++------ src/state/queries/feed.ts | 10 ++- src/view/com/feeds/ProfileFeedgens.tsx | 105 +++++++++++-------------- src/view/com/lists/ProfileLists.tsx | 32 ++++---- src/view/screens/Feeds.tsx | 9 ++- 5 files changed, 153 insertions(+), 105 deletions(-) diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx index bd0649097e..7f3cb88ff3 100644 --- a/src/components/FeedCard.tsx +++ b/src/components/FeedCard.tsx @@ -8,6 +8,7 @@ import { } from '@atproto/api' import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' import { @@ -16,6 +17,7 @@ import { useRemoveFeedMutation, } from '#/state/queries/preferences' import {sanitizeHandle} from 'lib/strings/handles' +import {precacheFeedFromGeneratorView, precacheList} from 'state/queries/feed' import {useSession} from 'state/session' import {UserAvatar} from '#/view/com/util/UserAvatar' import * as Toast from 'view/com/util/Toast' @@ -31,10 +33,7 @@ import * as Prompt from '#/components/Prompt' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' -export function Default({ - type, - view, -}: +type Props = | { type: 'feed' view: AppBskyFeedDefs.GeneratorView @@ -42,15 +41,24 @@ export function Default({ | { type: 'list' view: AppBskyGraphDefs.ListView - }) { + } + +export function Default(props: Props) { + const {type, view} = props const displayName = type === 'feed' ? view.displayName : view.name + const purpose = type === 'list' ? view.purpose : undefined return ( - +
- - + +
{type === 'feed' && } @@ -60,15 +68,31 @@ export function Default({ } export function Link({ + type, + view, + label, children, - feed, -}: { - feed: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView -} & Omit) { +}: Props & Omit) { + const queryClient = useQueryClient() + const href = React.useMemo(() => { - return createProfileFeedHref({feed}) - }, [feed]) - return {children} + return createProfileFeedHref({feed: view}) + }, [view]) + + return ( + { + if (type === 'feed') { + precacheFeedFromGeneratorView(queryClient, view) + } else { + precacheList(queryClient, view) + } + }}> + {children} + + ) } export function Outer({children}: {children: React.ReactNode}) { @@ -108,9 +132,13 @@ export function AvatarPlaceholder({size = 40}: Omit) { export function TitleAndByline({ title, creator, + type, + purpose, }: { title: string creator?: AppBskyActorDefs.ProfileViewBasic + type: 'feed' | 'list' + purpose?: AppBskyGraphDefs.ListView['purpose'] }) { const t = useTheme() @@ -123,7 +151,15 @@ export function TitleAndByline({ - Feed by {sanitizeHandle(creator.handle, '@')} + {type === 'list' && purpose === 'app.bsky.graph.defs#curatelist' ? ( + List by {sanitizeHandle(creator.handle, '@')} + ) : type === 'list' && purpose === 'app.bsky.graph.defs#modlist' ? ( + + Moderation list by {sanitizeHandle(creator.handle, '@')} + + ) : ( + Feed by {sanitizeHandle(creator.handle, '@')} + )} )} @@ -184,13 +220,31 @@ export function Likes({count}: {count: number}) { ) } -export function Action({uri, pin}: {uri: string; pin?: boolean}) { +export function Action({ + uri, + pin, + type, + purpose, +}: { + uri: string + pin?: boolean + type: 'feed' | 'list' + purpose?: AppBskyGraphDefs.ListView['purpose'] +}) { const {hasSession} = useSession() - if (!hasSession) return null - return + if (!hasSession || purpose !== 'app.bsky.graph.defs#curatelist') return null + return } -function ActionInner({uri, pin}: {uri: string; pin?: boolean}) { +function ActionInner({ + uri, + pin, + type, +}: { + uri: string + pin?: boolean + type: 'feed' | 'list' +}) { const {_} = useLingui() const {data: preferences} = usePreferencesQuery() const {isPending: isAddSavedFeedPending, mutateAsync: saveFeeds} = @@ -198,9 +252,7 @@ function ActionInner({uri, pin}: {uri: string; pin?: boolean}) { const {isPending: isRemovePending, mutateAsync: removeFeed} = useRemoveFeedMutation() const savedFeedConfig = React.useMemo(() => { - return preferences?.savedFeeds?.find( - feed => feed.type === 'feed' && feed.value === uri, - ) + return preferences?.savedFeeds?.find(feed => feed.value === uri) }, [preferences?.savedFeeds, uri]) const removePromptControl = Prompt.usePromptControl() const isPending = isAddSavedFeedPending || isRemovePending @@ -216,7 +268,7 @@ function ActionInner({uri, pin}: {uri: string; pin?: boolean}) { } else { await saveFeeds([ { - type: 'feed', + type, value: uri, pinned: pin || false, }, @@ -228,7 +280,7 @@ function ActionInner({uri, pin}: {uri: string; pin?: boolean}) { Toast.show(_(msg`Failed to update feeds`)) } }, - [_, pin, saveFeeds, removeFeed, uri, savedFeedConfig], + [_, pin, saveFeeds, removeFeed, uri, savedFeedConfig, type], ) const onPrompRemoveFeed = React.useCallback( diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index 972dbf995a..e5d6151775 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -578,7 +578,7 @@ function precacheFeed(queryClient: QueryClient, hydratedFeed: FeedSourceInfo) { ) } -function precacheList( +export function precacheList( queryClient: QueryClient, list: AppBskyGraphDefs.ListView, ) { @@ -588,3 +588,11 @@ function precacheList( list, ) } + +export function precacheFeedFromGeneratorView( + queryClient: QueryClient, + view: AppBskyFeedDefs.GeneratorView, +) { + const hydratedFeed = hydrateFeedGenerator(view) + precacheFeed(queryClient, hydratedFeed) +} diff --git a/src/view/com/feeds/ProfileFeedgens.tsx b/src/view/com/feeds/ProfileFeedgens.tsx index 197f35e4d0..ec1a55e22e 100644 --- a/src/view/com/feeds/ProfileFeedgens.tsx +++ b/src/view/com/feeds/ProfileFeedgens.tsx @@ -3,7 +3,6 @@ import { findNodeHandle, ListRenderItemInfo, StyleProp, - StyleSheet, View, ViewStyle, } from 'react-native' @@ -12,18 +11,17 @@ import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' import {cleanError} from '#/lib/strings/errors' -import {useTheme} from '#/lib/ThemeContext' import {logger} from '#/logger' import {isNative, isWeb} from '#/platform/detection' -import {hydrateFeedGenerator} from '#/state/queries/feed' import {usePreferencesQuery} from '#/state/queries/preferences' import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens' import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {EmptyState} from 'view/com/util/EmptyState' +import {atoms as a, useTheme} from '#/alf' +import * as FeedCard from '#/components/FeedCard' import {ErrorMessage} from '../util/error/ErrorMessage' import {List, ListRef} from '../util/List' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' -import {FeedSourceCardLoaded} from './FeedSourceCard' const LOADING = {_reactKey: '__loading__'} const EMPTY = {_reactKey: '__empty__'} @@ -52,7 +50,7 @@ export const ProfileFeedgens = React.forwardRef< ref, ) { const {_} = useLingui() - const theme = useTheme() + const t = useTheme() const [isPTRing, setIsPTRing] = React.useState(false) const opts = React.useMemo(() => ({enabled}), [enabled]) const { @@ -79,10 +77,9 @@ export const ProfileFeedgens = React.forwardRef< items = items.concat([EMPTY]) } else if (data?.pages) { for (const page of data?.pages) { - items = items.concat(page.feeds.map(feed => hydrateFeedGenerator(feed))) + items = items.concat(page.feeds) } - } - if (isError && !isEmpty) { + } else if (isError && !isEmpty) { items = items.concat([LOAD_MORE_ERROR_ITEM]) } return items @@ -132,48 +129,46 @@ export const ProfileFeedgens = React.forwardRef< // rendering // = - const renderItemInner = React.useCallback( - ({item, index}: ListRenderItemInfo) => { - if (item === EMPTY) { - return ( - - ) - } else if (item === ERROR_ITEM) { - return ( - - ) - } else if (item === LOAD_MORE_ERROR_ITEM) { - return ( - - ) - } else if (item === LOADING) { - return - } - if (preferences) { - return ( - - ) - } - return null - }, - [error, refetch, onPressRetryLoadMore, preferences, _], - ) + const renderItem = ({item, index}: ListRenderItemInfo) => { + if (item === EMPTY) { + return ( + + ) + } else if (item === ERROR_ITEM) { + return ( + + ) + } else if (item === LOAD_MORE_ERROR_ITEM) { + return ( + + ) + } else if (item === LOADING) { + return + } + if (preferences) { + return ( + + + + ) + } + return null + } React.useEffect(() => { if (enabled && scrollElRef.current) { @@ -189,12 +184,12 @@ export const ProfileFeedgens = React.forwardRef< ref={scrollElRef} data={items} keyExtractor={(item: any) => item._reactKey || item.uri} - renderItem={renderItemInner} + renderItem={renderItem} refreshing={isPTRing} onRefresh={onRefresh} headerOffset={headerOffset} contentContainerStyle={isNative && {paddingBottom: headerOffset + 100}} - indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'} + indicatorStyle={t.name === 'light' ? 'black' : 'white'} removeClippedSubviews={true} // @ts-ignore our .web version only -prf desktopFixedHeight @@ -203,9 +198,3 @@ export const ProfileFeedgens = React.forwardRef< ) }) - -const styles = StyleSheet.create({ - item: { - paddingHorizontal: 18, - }, -}) diff --git a/src/view/com/lists/ProfileLists.tsx b/src/view/com/lists/ProfileLists.tsx index e7fdfe4bd5..62c944efcb 100644 --- a/src/view/com/lists/ProfileLists.tsx +++ b/src/view/com/lists/ProfileLists.tsx @@ -3,7 +3,6 @@ import { findNodeHandle, ListRenderItemInfo, StyleProp, - StyleSheet, View, ViewStyle, } from 'react-native' @@ -12,17 +11,17 @@ import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' import {cleanError} from '#/lib/strings/errors' -import {useTheme} from '#/lib/ThemeContext' import {logger} from '#/logger' import {isNative, isWeb} from '#/platform/detection' import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists' import {useAnalytics} from 'lib/analytics/analytics' import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {EmptyState} from 'view/com/util/EmptyState' +import {atoms as a, useTheme} from '#/alf' +import * as FeedCard from '#/components/FeedCard' import {ErrorMessage} from '../util/error/ErrorMessage' import {List, ListRef} from '../util/List' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' -import {ListCard} from './ListCard' const LOADING = {_reactKey: '__loading__'} const EMPTY = {_reactKey: '__empty__'} @@ -48,7 +47,7 @@ export const ProfileLists = React.forwardRef( {did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag}, ref, ) { - const theme = useTheme() + const t = useTheme() const {track} = useAnalytics() const {_} = useLingui() const [isPTRing, setIsPTRing] = React.useState(false) @@ -166,15 +165,18 @@ export const ProfileLists = React.forwardRef( return } return ( - + + + ) }, - [error, refetch, onPressRetryLoadMore, _], + [error, refetch, onPressRetryLoadMore, _, t.atoms.border_contrast_low], ) React.useEffect(() => { @@ -198,7 +200,7 @@ export const ProfileLists = React.forwardRef( contentContainerStyle={ isNative && {paddingBottom: headerOffset + 100} } - indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'} + indicatorStyle={t.name === 'light' ? 'black' : 'white'} removeClippedSubviews={true} // @ts-ignore our .web version only -prf desktopFixedHeight @@ -208,9 +210,3 @@ export const ProfileLists = React.forwardRef( ) }, ) - -const styles = StyleSheet.create({ - item: { - paddingHorizontal: 18, - }, -}) diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index 70437a9e76..2e5b485136 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -627,7 +627,7 @@ function FollowingFeed() { fill={t.palette.white} /> - + ) @@ -644,7 +644,7 @@ function SavedFeed({ savedFeed.type === 'feed' ? savedFeed.view.displayName : savedFeed.view.name return ( - + {({hovered, pressed}) => ( - + From f089f4578131e83cd177b7809ce0f7b75779dfdc Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 21 Jun 2024 21:38:04 -0700 Subject: [PATCH 092/127] Starter Packs (#4332) Co-authored-by: Dan Abramov Co-authored-by: Paul Frazee Co-authored-by: Eric Bailey Co-authored-by: Samuel Newman --- __tests__/lib/string.test.ts | 181 +++++ app.config.js | 18 +- .../icons/qrCode_stroke2_corner0_rounded.svg | 1 + assets/icons/starterPack.svg | 1 + assets/icons/starter_pack_icon.svg | 1 + assets/logo.png | Bin 0 -> 10126 bytes bskyweb/cmd/bskyweb/server.go | 4 + .../.well-known/apple-app-site-association | 6 +- modules/BlueskyClip/AppDelegate.swift | 32 + .../App-Icon-1024x1024@1x.png | Bin 0 -> 473960 bytes .../AppIcon.appiconset/Contents.json | 14 + .../BlueskyClip/Images.xcassets/Contents.json | 6 + modules/BlueskyClip/ViewController.swift | 133 ++++ .../android/build.gradle | 47 ++ .../android/src/main/AndroidManifest.xml | 2 + .../ExpoBlueskyDevicePrefsModule.kt | 10 + .../referrer/ExpoBlueskyReferrerModule.kt | 54 ++ .../expo-module.config.json | 12 + modules/expo-bluesky-swiss-army/index.ts | 4 + .../ExpoBlueskyDevicePrefsModule.swift | 23 + .../ios/ExpoBlueskySwissArmy.podspec | 21 + .../Referrer/ExpoBlueskyReferrerModule.swift | 7 + .../src/DevicePrefs/index.ios.ts | 18 + .../src/DevicePrefs/index.ts | 16 + .../src/NotImplemented.ts | 16 + .../src/Referrer/index.android.ts | 9 + .../src/Referrer/index.ts | 7 + .../src/Referrer/types.ts | 7 + package.json | 5 +- .../withAppEntitlements.js | 16 + .../withClipEntitlements.js | 32 + .../withClipInfoPlist.js | 38 ++ .../starterPackAppClipExtension/withFiles.js | 40 ++ .../withStarterPackAppClip.js | 40 ++ .../withXcodeTarget.js | 91 +++ scripts/updateExtensions.sh | 9 + src/App.native.tsx | 9 +- src/App.web.tsx | 9 +- src/Navigation.tsx | 23 + src/components/LinearGradientBackground.tsx | 23 + src/components/NewskieDialog.tsx | 70 +- src/components/ProfileCard.tsx | 91 +++ .../ReportDialog/SelectReportOptionView.tsx | 3 + src/components/ReportDialog/types.ts | 2 +- src/components/StarterPack/Main/FeedsList.tsx | 68 ++ .../StarterPack/Main/ProfilesList.tsx | 119 ++++ .../StarterPack/ProfileStarterPacks.tsx | 320 +++++++++ src/components/StarterPack/QrCode.tsx | 119 ++++ src/components/StarterPack/QrCodeDialog.tsx | 201 ++++++ src/components/StarterPack/ShareDialog.tsx | 180 +++++ .../StarterPack/StarterPackCard.tsx | 117 ++++ .../StarterPack/Wizard/ScreenTransition.tsx | 31 + .../Wizard/WizardEditListDialog.tsx | 152 +++++ .../StarterPack/Wizard/WizardListCard.tsx | 182 +++++ src/components/forms/TextField.tsx | 2 + .../hooks/useStarterPackEntry.native.ts | 68 ++ src/components/hooks/useStarterPackEntry.ts | 29 + src/components/icons/QrCode.tsx | 5 + src/components/icons/StarterPack.tsx | 8 + src/components/icons/TEMPLATE.tsx | 31 +- src/components/icons/common.ts | 32 - src/components/icons/common.tsx | 59 ++ src/lib/browser.native.ts | 1 + src/lib/browser.ts | 2 + src/lib/generate-starterpack.ts | 164 +++++ src/lib/hooks/useBottomBarOffset.ts | 14 + src/lib/hooks/useNotificationHandler.ts | 2 + .../create-sanitized-display-name.ts | 21 + src/lib/moderation/useReportOptions.ts | 9 + src/lib/routes/links.ts | 17 + src/lib/routes/types.ts | 12 + src/lib/statsig/events.ts | 35 +- src/lib/statsig/gates.ts | 1 + src/lib/strings/starter-pack.ts | 101 +++ src/routes.ts | 4 + src/screens/Login/LoginForm.tsx | 3 + src/screens/Login/ScreenTransition.tsx | 11 +- src/screens/Onboarding/StepFinished.tsx | 117 +++- src/screens/Profile/Header/DisplayName.tsx | 6 +- src/screens/Signup/index.tsx | 40 +- .../StarterPack/StarterPackLandingScreen.tsx | 378 +++++++++++ src/screens/StarterPack/StarterPackScreen.tsx | 627 ++++++++++++++++++ src/screens/StarterPack/Wizard/State.tsx | 163 +++++ .../StarterPack/Wizard/StepDetails.tsx | 84 +++ src/screens/StarterPack/Wizard/StepFeeds.tsx | 113 ++++ .../StarterPack/Wizard/StepFinished.tsx | 0 .../StarterPack/Wizard/StepProfiles.tsx | 101 +++ src/screens/StarterPack/Wizard/index.tsx | 575 ++++++++++++++++ src/state/persisted/schema.ts | 2 + src/state/preferences/index.tsx | 5 +- src/state/preferences/used-starter-packs.tsx | 37 ++ src/state/queries/actor-search.ts | 46 +- src/state/queries/actor-starter-packs.ts | 47 ++ src/state/queries/feed.ts | 17 + src/state/queries/list-members.ts | 19 +- src/state/queries/notifications/feed.ts | 11 +- src/state/queries/notifications/types.ts | 49 +- src/state/queries/notifications/util.ts | 83 ++- src/state/queries/profile-lists.ts | 10 +- src/state/queries/shorten-link.ts | 23 + src/state/queries/starter-packs.ts | 317 +++++++++ src/state/session/agent.ts | 12 - src/state/shell/logged-out.tsx | 17 +- src/state/shell/starter-pack.tsx | 25 + src/view/com/auth/LoggedOut.tsx | 42 +- src/view/com/feeds/FeedSourceCard.tsx | 3 + src/view/com/notifications/FeedItem.tsx | 87 ++- src/view/com/profile/FollowButton.tsx | 11 +- src/view/com/profile/ProfileCard.tsx | 6 +- src/view/com/profile/ProfileSubpageHeader.tsx | 10 +- src/view/screens/Home.tsx | 2 +- src/view/screens/Profile.tsx | 115 ++-- src/view/screens/Storybook/Icons.tsx | 8 + src/view/shell/desktop/LeftNav.tsx | 8 +- yarn.lock | 86 ++- 115 files changed, 6336 insertions(+), 237 deletions(-) create mode 100644 assets/icons/qrCode_stroke2_corner0_rounded.svg create mode 100644 assets/icons/starterPack.svg create mode 100644 assets/icons/starter_pack_icon.svg create mode 100644 assets/logo.png create mode 100644 modules/BlueskyClip/AppDelegate.swift create mode 100644 modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png create mode 100644 modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 modules/BlueskyClip/Images.xcassets/Contents.json create mode 100644 modules/BlueskyClip/ViewController.swift create mode 100644 modules/expo-bluesky-swiss-army/android/build.gradle create mode 100644 modules/expo-bluesky-swiss-army/android/src/main/AndroidManifest.xml create mode 100644 modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/deviceprefs/ExpoBlueskyDevicePrefsModule.kt create mode 100644 modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/referrer/ExpoBlueskyReferrerModule.kt create mode 100644 modules/expo-bluesky-swiss-army/expo-module.config.json create mode 100644 modules/expo-bluesky-swiss-army/index.ts create mode 100644 modules/expo-bluesky-swiss-army/ios/DevicePrefs/ExpoBlueskyDevicePrefsModule.swift create mode 100644 modules/expo-bluesky-swiss-army/ios/ExpoBlueskySwissArmy.podspec create mode 100644 modules/expo-bluesky-swiss-army/ios/Referrer/ExpoBlueskyReferrerModule.swift create mode 100644 modules/expo-bluesky-swiss-army/src/DevicePrefs/index.ios.ts create mode 100644 modules/expo-bluesky-swiss-army/src/DevicePrefs/index.ts create mode 100644 modules/expo-bluesky-swiss-army/src/NotImplemented.ts create mode 100644 modules/expo-bluesky-swiss-army/src/Referrer/index.android.ts create mode 100644 modules/expo-bluesky-swiss-army/src/Referrer/index.ts create mode 100644 modules/expo-bluesky-swiss-army/src/Referrer/types.ts create mode 100644 plugins/starterPackAppClipExtension/withAppEntitlements.js create mode 100644 plugins/starterPackAppClipExtension/withClipEntitlements.js create mode 100644 plugins/starterPackAppClipExtension/withClipInfoPlist.js create mode 100644 plugins/starterPackAppClipExtension/withFiles.js create mode 100644 plugins/starterPackAppClipExtension/withStarterPackAppClip.js create mode 100644 plugins/starterPackAppClipExtension/withXcodeTarget.js create mode 100644 src/components/LinearGradientBackground.tsx create mode 100644 src/components/ProfileCard.tsx create mode 100644 src/components/StarterPack/Main/FeedsList.tsx create mode 100644 src/components/StarterPack/Main/ProfilesList.tsx create mode 100644 src/components/StarterPack/ProfileStarterPacks.tsx create mode 100644 src/components/StarterPack/QrCode.tsx create mode 100644 src/components/StarterPack/QrCodeDialog.tsx create mode 100644 src/components/StarterPack/ShareDialog.tsx create mode 100644 src/components/StarterPack/StarterPackCard.tsx create mode 100644 src/components/StarterPack/Wizard/ScreenTransition.tsx create mode 100644 src/components/StarterPack/Wizard/WizardEditListDialog.tsx create mode 100644 src/components/StarterPack/Wizard/WizardListCard.tsx create mode 100644 src/components/hooks/useStarterPackEntry.native.ts create mode 100644 src/components/hooks/useStarterPackEntry.ts create mode 100644 src/components/icons/QrCode.tsx create mode 100644 src/components/icons/StarterPack.tsx delete mode 100644 src/components/icons/common.ts create mode 100644 src/components/icons/common.tsx create mode 100644 src/lib/generate-starterpack.ts create mode 100644 src/lib/hooks/useBottomBarOffset.ts create mode 100644 src/lib/moderation/create-sanitized-display-name.ts create mode 100644 src/lib/strings/starter-pack.ts create mode 100644 src/screens/StarterPack/StarterPackLandingScreen.tsx create mode 100644 src/screens/StarterPack/StarterPackScreen.tsx create mode 100644 src/screens/StarterPack/Wizard/State.tsx create mode 100644 src/screens/StarterPack/Wizard/StepDetails.tsx create mode 100644 src/screens/StarterPack/Wizard/StepFeeds.tsx create mode 100644 src/screens/StarterPack/Wizard/StepFinished.tsx create mode 100644 src/screens/StarterPack/Wizard/StepProfiles.tsx create mode 100644 src/screens/StarterPack/Wizard/index.tsx create mode 100644 src/state/preferences/used-starter-packs.tsx create mode 100644 src/state/queries/actor-starter-packs.ts create mode 100644 src/state/queries/shorten-link.ts create mode 100644 src/state/queries/starter-packs.ts create mode 100644 src/state/shell/starter-pack.tsx diff --git a/__tests__/lib/string.test.ts b/__tests__/lib/string.test.ts index 30072ccb1d..0da9551e30 100644 --- a/__tests__/lib/string.test.ts +++ b/__tests__/lib/string.test.ts @@ -1,6 +1,11 @@ import {RichText} from '@atproto/api' import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player' +import { + createStarterPackGooglePlayUri, + createStarterPackLinkFromAndroidReferrer, + parseStarterPackUri, +} from 'lib/strings/starter-pack' import {cleanError} from '../../src/lib/strings/errors' import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles' import {enforceLen} from '../../src/lib/strings/helpers' @@ -796,3 +801,179 @@ describe('parseEmbedPlayerFromUrl', () => { } }) }) + +describe('createStarterPackLinkFromAndroidReferrer', () => { + const validOutput = 'at://haileyok.com/app.bsky.graph.starterpack/rkey' + + it('returns a link when input contains utm_source and utm_content', () => { + expect( + createStarterPackLinkFromAndroidReferrer( + 'utm_source=bluesky&utm_content=starterpack_haileyok.com_rkey', + ), + ).toEqual(validOutput) + + expect( + createStarterPackLinkFromAndroidReferrer( + 'utm_source=bluesky&utm_content=starterpack_test-lover-9000.com_rkey', + ), + ).toEqual('at://test-lover-9000.com/app.bsky.graph.starterpack/rkey') + }) + + it('returns a link when input contains utm_source and utm_content in different order', () => { + expect( + createStarterPackLinkFromAndroidReferrer( + 'utm_content=starterpack_haileyok.com_rkey&utm_source=bluesky', + ), + ).toEqual(validOutput) + }) + + it('returns a link when input contains other parameters as well', () => { + expect( + createStarterPackLinkFromAndroidReferrer( + 'utm_source=bluesky&utm_medium=starterpack&utm_content=starterpack_haileyok.com_rkey', + ), + ).toEqual(validOutput) + }) + + it('returns null when utm_source is not present', () => { + expect( + createStarterPackLinkFromAndroidReferrer( + 'utm_content=starterpack_haileyok.com_rkey', + ), + ).toEqual(null) + }) + + it('returns null when utm_content is not present', () => { + expect( + createStarterPackLinkFromAndroidReferrer('utm_source=bluesky'), + ).toEqual(null) + }) + + it('returns null when utm_content is malformed', () => { + expect( + createStarterPackLinkFromAndroidReferrer( + 'utm_content=starterpack_haileyok.com', + ), + ).toEqual(null) + + expect( + createStarterPackLinkFromAndroidReferrer('utm_content=starterpack'), + ).toEqual(null) + + expect( + createStarterPackLinkFromAndroidReferrer( + 'utm_content=starterpack_haileyok.com_rkey_more', + ), + ).toEqual(null) + + expect( + createStarterPackLinkFromAndroidReferrer( + 'utm_content=notastarterpack_haileyok.com_rkey', + ), + ).toEqual(null) + }) +}) + +describe('parseStarterPackHttpUri', () => { + const baseUri = 'https://bsky.app/start' + + it('returns a valid at uri when http uri is valid', () => { + const validHttpUri = `${baseUri}/haileyok.com/rkey` + expect(parseStarterPackUri(validHttpUri)).toEqual({ + name: 'haileyok.com', + rkey: 'rkey', + }) + + const validHttpUri2 = `${baseUri}/haileyok.com/ilovetesting` + expect(parseStarterPackUri(validHttpUri2)).toEqual({ + name: 'haileyok.com', + rkey: 'ilovetesting', + }) + + const validHttpUri3 = `${baseUri}/testlover9000.com/rkey` + expect(parseStarterPackUri(validHttpUri3)).toEqual({ + name: 'testlover9000.com', + rkey: 'rkey', + }) + }) + + it('returns null when there is no rkey', () => { + const validHttpUri = `${baseUri}/haileyok.com` + expect(parseStarterPackUri(validHttpUri)).toEqual(null) + }) + + it('returns null when there is an extra path', () => { + const validHttpUri = `${baseUri}/haileyok.com/rkey/other` + expect(parseStarterPackUri(validHttpUri)).toEqual(null) + }) + + it('returns null when there is no handle or rkey', () => { + const validHttpUri = `${baseUri}` + expect(parseStarterPackUri(validHttpUri)).toEqual(null) + }) + + it('returns null when the route is not /start or /starter-pack', () => { + const validHttpUri = 'https://bsky.app/start/haileyok.com/rkey' + expect(parseStarterPackUri(validHttpUri)).toEqual({ + name: 'haileyok.com', + rkey: 'rkey', + }) + + const validHttpUri2 = 'https://bsky.app/starter-pack/haileyok.com/rkey' + expect(parseStarterPackUri(validHttpUri2)).toEqual({ + name: 'haileyok.com', + rkey: 'rkey', + }) + + const invalidHttpUri = 'https://bsky.app/profile/haileyok.com/rkey' + expect(parseStarterPackUri(invalidHttpUri)).toEqual(null) + }) + + it('returns the at uri when the input is a valid starterpack at uri', () => { + const validAtUri = 'at://did:123/app.bsky.graph.starterpack/rkey' + expect(parseStarterPackUri(validAtUri)).toEqual({ + name: 'did:123', + rkey: 'rkey', + }) + }) + + it('returns null when the at uri has no rkey', () => { + const validAtUri = 'at://did:123/app.bsky.graph.starterpack' + expect(parseStarterPackUri(validAtUri)).toEqual(null) + }) + + it('returns null when the collection is not app.bsky.graph.starterpack', () => { + const validAtUri = 'at://did:123/app.bsky.graph.list/rkey' + expect(parseStarterPackUri(validAtUri)).toEqual(null) + }) + + it('returns null when the input is undefined', () => { + expect(parseStarterPackUri(undefined)).toEqual(null) + }) +}) + +describe('createStarterPackGooglePlayUri', () => { + const base = + 'https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack_' + + it('returns valid google play uri when input is valid', () => { + expect(createStarterPackGooglePlayUri('name', 'rkey')).toEqual( + `${base}name_rkey`, + ) + }) + + it('returns null when no rkey is supplied', () => { + // @ts-expect-error test + expect(createStarterPackGooglePlayUri('name', undefined)).toEqual(null) + }) + + it('returns null when no name or rkey are supplied', () => { + // @ts-expect-error test + expect(createStarterPackGooglePlayUri(undefined, undefined)).toEqual(null) + }) + + it('returns null when rkey is supplied but no name', () => { + // @ts-expect-error test + expect(createStarterPackGooglePlayUri(undefined, 'rkey')).toEqual(null) + }) +}) diff --git a/app.config.js b/app.config.js index eafacc6cc1..57d4305865 100644 --- a/app.config.js +++ b/app.config.js @@ -39,6 +39,17 @@ module.exports = function (config) { const IS_TESTFLIGHT = process.env.EXPO_PUBLIC_ENV === 'testflight' const IS_PRODUCTION = process.env.EXPO_PUBLIC_ENV === 'production' + const ASSOCIATED_DOMAINS = [ + 'applinks:bsky.app', + 'applinks:staging.bsky.app', + 'appclips:bsky.app', + 'appclips:go.bsky.app', // Allows App Clip to work when scanning QR codes + // When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port. + ...(IS_DEV || IS_TESTFLIGHT + ? ['appclips:sptesting.haileyok.com', 'applinks:sptesting.haileyok.com'] + : []), + ] + const UPDATES_CHANNEL = IS_TESTFLIGHT ? 'testflight' : IS_PRODUCTION @@ -83,7 +94,7 @@ module.exports = function (config) { NSPhotoLibraryUsageDescription: 'Used for profile pictures, posts, and other kinds of content', }, - associatedDomains: ['applinks:bsky.app', 'applinks:staging.bsky.app'], + associatedDomains: ASSOCIATED_DOMAINS, splash: { ...SPLASH_CONFIG, dark: DARK_SPLASH_CONFIG, @@ -202,6 +213,7 @@ module.exports = function (config) { sounds: PLATFORM === 'ios' ? ['assets/dm.aiff'] : ['assets/dm.mp3'], }, ], + './plugins/starterPackAppClipExtension/withStarterPackAppClip.js', './plugins/withAndroidManifestPlugin.js', './plugins/withAndroidManifestFCMIconPlugin.js', './plugins/withAndroidStylesWindowBackgroundPlugin.js', @@ -234,6 +246,10 @@ module.exports = function (config) { ], }, }, + { + targetName: 'BlueskyClip', + bundleIdentifier: 'xyz.blueskyweb.app.AppClip', + }, ], }, }, diff --git a/assets/icons/qrCode_stroke2_corner0_rounded.svg b/assets/icons/qrCode_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..b17db39533 --- /dev/null +++ b/assets/icons/qrCode_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/starterPack.svg b/assets/icons/starterPack.svg new file mode 100644 index 0000000000..7f0df55952 --- /dev/null +++ b/assets/icons/starterPack.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/starter_pack_icon.svg b/assets/icons/starter_pack_icon.svg new file mode 100644 index 0000000000..47a2f49b64 --- /dev/null +++ b/assets/icons/starter_pack_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cc2d660341e380d1150b1f86735ac78d8394e87f GIT binary patch literal 10126 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 U`^y*j|JebmN}7rl^42f^4?+A_Y5)KL literal 0 HcmV?d00001 diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 6d32e0e212..96fb07ddfe 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -223,6 +223,10 @@ func serve(cctx *cli.Context) error { e.GET("/profile/:handleOrDID/post/:rkey/liked-by", server.WebGeneric) e.GET("/profile/:handleOrDID/post/:rkey/reposted-by", server.WebGeneric) + // starter packs + e.GET("/starter-pack/:handleOrDID/:rkey", server.WebGeneric) + e.GET("/start/:handleOrDID/:rkey", server.WebGeneric) + if linkHost != "" { linkUrl, err := url.Parse(linkHost) if err != nil { diff --git a/bskyweb/static/.well-known/apple-app-site-association b/bskyweb/static/.well-known/apple-app-site-association index 232acdf255..0a05fa35f4 100644 --- a/bskyweb/static/.well-known/apple-app-site-association +++ b/bskyweb/static/.well-known/apple-app-site-association @@ -1,6 +1,8 @@ { "applinks": { - "apps": [], + "appclips": { + "apps": ["B3LX46C5HS.xyz.blueskyweb.app.AppClip"] + }, "details": [ { "appID": "B3LX46C5HS.xyz.blueskyweb.app", @@ -10,4 +12,4 @@ } ] } -} \ No newline at end of file +} diff --git a/modules/BlueskyClip/AppDelegate.swift b/modules/BlueskyClip/AppDelegate.swift new file mode 100644 index 0000000000..684194953c --- /dev/null +++ b/modules/BlueskyClip/AppDelegate.swift @@ -0,0 +1,32 @@ +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + var controller: ViewController? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + let window = UIWindow() + self.window = UIWindow() + + let controller = ViewController(window: window) + self.controller = controller + + window.rootViewController = self.controller + window.makeKeyAndVisible() + + return true + } + + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + self.controller?.handleURL(url: url) + return true + } + + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + if let incomingURL = userActivity.webpageURL { + self.controller?.handleURL(url: incomingURL) + } + return true + } +} diff --git a/modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png b/modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..75ce4b813acb8a0fa79a8c766c0c5c8d3afd9457 GIT binary patch literal 473960 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

H~IxaQ>!ovzE5c(jLI`u&*6mCyM@@HSA zn(>MZhUse=ivRW5h9ihacysEtS<6|fE;$@OGrQud{&r`egYD1@*K*(zVWGTz^mmsa zI;ltYo=5WzXl=;v@##=YmJ6?y(WQVwo|vp-b~ZE-Ux#Mxi~Of)K6pU9k`etO0i{}{ zkB>ixretY!F++%m#}8 zD%Z{Soxyk)+0idPd-u*3+rvg28u-zQLQ<4fABK7)X?}%ngLkWX_8msbIpwtOGhS^}T4V4L%LZm>;TJk@GlPBH2GTpEavXVO>hI{=Yo` zyEEWDWukEHp>{%$Wt!DNaGT@HiQYl1<35MxwOGFMvHx3`7j|?4nw_Q&Y@lcPTH)U3 zyeYV~zvj?Cjo%E236a9NR9J5P@Slmx#O_?WjV={?e*e{^IHff^^Csq?k_)k7xQ9{%Z!RFZ#leTpsm3jq} zwQtG$Qw6pbh7_6Rk6?ia_^kt;k?)#EGF_0U)WQ46Qw_$ZDoS07J&uU=tmUuZXhhK; zJ`tLishezOEQ{t7geW>uX~yWoW{3#?G(H*s>n%iJyqoQqs}5wpDZ3b$!!q9ZGjo|4 z=_aI{Rgoi4ghv8&_*myyr>B~6R{^V`oq5>mM~5VO4}E0A6{~Pz$h9E>3LN{ zo)6}QgEXcAn%Gg{OVQyui@NoT$mkqy(S9Ju((e3q8l}c|aDFwcqV5QX6psm*5;HQq z`v)-Scgrrm&%RcMt@1tNd;sHYK_K#pzj!wKj1P(8S)oG@c@_8`#Y-+s0~hHiGv(0K zZT8&wuKC&F=qqAuujFlhiy0op^6ZtucPh;tOq#fMNCc5=eIK3}@yGYw=aWmsRScl* zq7ueSnz8L#Oy5h6wq%NvayBmIpAtAuouin8obGB|hF#g7Tkk1-30_VPm&vSO74>WZ z8G7byOTQAE0$!pV84%r(>zKn#*VGbbHPRc$rKT{>#z>AnD8;jL#PF0t?@v<_7(z;D ze*XOLeof&~%4G0MIQSS{aHNaaGr8Ar1A(dKVqNCAvWrT@!l4%78nZKIEzb_nXEI?S z&2re`&6{kon`nF9yux@>OFk?t(!|k%4{jgtvG5525Uw!otZz7o0!SCSs(T4m6 zGVhE-F*6R1t!>4_)yiK&`i6A5UPwJHgo5E{~ z$GHf0JU0Dn#^wI85az)ads>HJmMt$ue~4gLEoa2dD-jUI&p-V6Y3kW4vWu8&X!|m% z$n@*aza3qKX1DVzNSiO4KZIy~9)0`Snqo|iv!nTraOdl_!$Yje5p1?H;L*hLFYw2( z9mf03vQOg|6q9I3l# zq1Re=WKdH!-(=StZ-0Po$3%)9)Q+$`b6e(T9f-S3c-@6lI}I<7MQ2BUm?vZQGxa?t z4QjwF!GN)TzF*hW$}zP*1WaToLyREuo3P6@1>-he@wGfT^o3at3tTksqHusq3gP{^ zuMQw0GEr_SZpgZ)7?eBsg@yKR>1 z`=?iYuaj`}ZEnW${sL{N(8hY<}rChO%2k<++JKMPsSS% zB^hsm6#Fmx5jtd05Jyfutk7N6v$09vM6N5>`OaWVOP>GrR|E7v{L%L=96qtYIr8&g z4Lo|?$lmTb_ZR&oC&GQW6$fXooZc5jzZ|jqqn%!nH3ZCL;I=`j~>hX!l12E-HTU^|6q@E5S7JirmC*z5{Y)6qs+; zdUo18p*J0cqJa;OSfOtW8uTQ-+li4c0TZ~~oZLb?uwv&7?Tnle`Mc2M=3xGaz#l7C zw^dVd22D;&fs{3%&a>OX@J8TmsHvQe%4J01=GOzkyZv1`j`*58DRfAac zG9-PM|2ZB2y8|A;8sDm>{h;P|LcN5thGK3a+Uu&8T>z*7H4pdKfAs&mN4^L!t26aC zRU!wh1mx$s8oES=&P^$S84^C7@W(!E)~#-|C12-)tZF^%M zu1pll@S)o)p?y&|0T?Lu3I%BcDIvv<+r&VNVT_M4nSajo1)@q03w=U={e=Cr2DrwM zzhq#G%1&0gbZYbp3LT0X&_G3|!qB!uWh~IE#Jn)mGY0u+>=;aHSAwy1Av%7!_yRns zvcU0ckvn;$H+oHh55?rv8+Ecrph01zZ5)_E`D&^E&k1Yuqu!`!8I$=shJv-~sli6X ze#H~s#16q9x*rvaTn($I2nPq9g7iH03b>o0fZFu6q>uqEigXl zEYNG}>WdOnS!UCaq{45wPb5)GlLpAgF|VyQ~ajNPfDF>F*mC4`jK zDzfr{LAsB%aEGhu*oQE&4;fW}ktAxdk%P-)egK&NVR0*VU(l7YMoGB78avzCqsvAp$p|UxhVU zxr~-cO)zx^pNY)~m6l&)Ym1f%t0G?P(6nAK=pdW}zaz{~AXvwQuuYMJ0quc~2&S&R zOQ477g7(I8HnwT}m!G&$UeBM}hM|#OE<=g$s)lp`vx%V}K`z5~@Pl;}F}s>(86Z;9 z`#H+YuM@RQ5JNlnT@rnM^kE`(1BGy zaGh(8!uhG=ynP>)Jn%QtoQwot@r@C+#ipyDs2Fx;( zC}1yPM~X%M$08>BV=G~=;P*jDfvmVTBN;Lm{b( zoe=?2jlg0(@;bOz+!s9|%x`^=g^Ii!eggClz%{YMT*EfJgM2K$g=TR4oTNj;;ag03Qi~ndoN&f0IsH<05n|ZEYZi zWapF4KJQ~Vn~*y_S+Cy)5}+E4MYJ2;XAm-Ek|!OF(e}_-c|5FR`zIvKw8F`0g{m== zoZtyDX-mVNiEXWJrU;L(5aG!&)AFEDVi^v#ovISy zCsIN>^i9x+j-9bQIvnfCQ5QseBw?<53NIFm)RH7r2JuyzocOf}W12>tIgEx;xM^?} z;=8`yR@)R507s{vik3fDt6N3qCFDE@E8#XWSs)%rn`0ZaB>{WP;x;6pth(*(MiczbMOx5Mq6;Z#9K{oTj1Br z@m3kyQsXdge3ic>vhNY3!?3x4e({5N-M}zCZgJfNHaHp_mW=sF*Lp5|>H|B@1B?0; z38T;?uzfM}RWv5yf~G**dMeDAB>I5yO9=chk&H8Nnb@gvQo*x-*4iDz}>}nuy5NisMmtsjV36CLkF>hajQk-rK-Qn2C z*vjx($cSNKFlyw%$WE4qk>*6*!)l-@PU)Vg8S8QnlUTsEm(kq@DY*;e@-DG7H6H3E zwK;mnk+;*K>4dRH_Fpd-_)T&VW?B^0wffU&px2>R zJ;;2HSeKK$-u#4?lCTq-nsz?!irgPwdeqZeBO&>3r-xJfTSdI(&oNSF`i7e?@RF5! z3rDM|1Cw`+h^)Kt2Fx2t`aBLEBtj~j$_VKI?SnMAp+#W=pqPm4vhA>>O1;vihp zJUm_UdZ_1#dTi-)?FHk;l)deNeVYI16$@Cef;k6v0c5I5n=(z1EaOqA}4I%9Tz-o))_a-i4H3X3@ z-L2qvfoJot!^idnSq232kb7zrTHYYE+#Vs0EN}D_L?=J-f$-yX5t84@ZB8o_{GcFT z$k*e->|+<2m>^1ZxFcp?cH553b4l|7G+wnmxn6z|z9UsBZV(ML68T5L!@TuaVPPNm zoPQ*F?L2(EPV{ya43OO#yKkhH$S4OP2uv92Rx|Q(=BPRj(yq#cpZ@pnJ@S4@CR(DF)w*OvOm** z_&6rNK)h}tq91DoQDLuVltdhi3mu~=9v}A~d9Ry|!8V)!F^+V`{QS?=pbNf4t~Gn2 zhm+Y;8bh$WwV^X{4HJ|Q!C@ZuHD0tGd3Jq(gH*%Yb$3dWgJej1!67A}h{f}RL{xrI z;-8tCcFw1)c79SzOM*WJPt6geFE(eM=g&!;Ot(MaCR;npqnFSsHAV2c zaQ^LSMbv9i>PmZ0m?=vEWFG~=kC zBDusm3bmg3rVGG({#bJ1b?8{cL`S4M>~K?6JKD&!GQ=0*0Q{Foo0AQZS~eN|^4T+S zXSsVNy1zAe`gvTmWc&#eX6S9NIGhATbNxn zV{1DA9SP6qhOJhNH*He>zUgc=&Nxp+Q4`|X;9!_5KIQr7gG;P%Q(3Ra$4<1atc>ak zaKuDR6}dScF*sKB4iCr)F*^6>>I+prgBFPQar`tdwjVKaqwol1skO&&FfG)NpdIAb zqUFeaqW2NR?bPtwA0eK=!}pj0N=2dtI;5ZB#z;KY75UFToMA6~rjMMqeMEhBJPNwh zL5QQomzxoMMLgblQ1`T6C_mEP@MZQ!+Xd2>Vwd*{98%mwac$8#@wnr<2R6R+Gm^Z9k4*8!YmWgcjeQt8?CGJ~;@Sp_y?IJg9^LYy z=rH^^CX!BlMt|%I)E+cHhv?gHmY|>0w(>aMb|HZrNue+0gV{GvUd#>2>{%HW3cZ5x8T=w(i1FoOaN0ERqyN_Zc?Ge88w6V_T$ek>3=JX z|J#gO1G(kO{^(3CjmK1pZ*d(R25sxk8FSxg6(a*Ma~^FCdTNo3%Xc8C<&jwn-VSe+ zFc&Mc&|ExTU4;EDb6Vs+J9?Bu^qDD(=aO4szE!lRuxH|=2%e&Ldq%ogZ zPsz~P4JE>^^7uq|%>xsa66~J+QeE>_pHC3Xfly_BqPCg_6V)gcFXU|w>T2SGLh;x~ zzjC1kQwT;YrAWQX?=Vnl%z5oD30=M_vt46|rX?o|3iEAocU62+4=#2`DTqwhRXI^- zHI+nq63T;ajSYq2DnilGMvL5NV#YvP#>ElXB8h;c}E+o?ginukU36CUy z`8?NJaT-dHy5R0_gy35`uAm17RXIyZh;6W|zFeA8&?zT)n^a*_(%cu4^E;U%8uSYJ zQK1@GbU{SC!qLQR3C@lkEusx6N>{mSYG)i9Z0T6ks99-A1oF9tTb(5u5iKnFr) z^jyRs2wB(f^7|#XV0kflQXc?fHd+i7bAx|&>WyNbCQeaa3;3L z$-15UQ|Q)!G39+98o$3hzt9)?Mape+`f)VJ#ygN;MC3X(JEX@<#+-I|UC(^5ZQ;P9 z<6!o+4OCY{Y4jn-Q!r{D=rpO4Gp~elNj>!us2g{lbMi^QWCk>8sgLUyR91BjC_iCr z-mJs~$fP6PJEBc~p>5d@N$gP&rV{GNS{G78HSJGJZnvYk1?Hsv?T^97c+CSn(u7|! zR)C8kXf3w~85uX*Xwn}!v~IVm5vZawmA#(!gPU1k5!reEn-A2tGBuI6j;VKSJbkqL zuZ}6b0r{6;DNOn`Ixjc>4fG%+r29Pj@0*hlse!rW)CZLym9{M=w^bv34NnTBPvXZx zf9o>Yc4v?nju3@{NblR|1%g3MeCz~kWdSs07D9#jRd3<8RV*ren;a;WdL*J);f>oI zO^taPvAvsKiM|EBV~dHE^gsN}Y^$``{`q55QaTivSTug3VhN<-4RARYTHOetx2D0G zR`~r0{X1a{Vl0foWs)5r=@GBSdJTsm*7i2nxB>p!!~|cm(>1|mI zC?!_OLe*Q4-tmYAbdg_PL=Xt~{mO@eQuO7pec;rB+L9D@Ast#E5|jTd)_;eDcuwIy zYbhG1B@0pvBag3Kjs3tNwp8oI;AoQ(Fp3&*U~Yo4Zj_Z1>lL3$K!B@4xwEY6hdvot zj5SXrv7Q>VPt+njG}!Q2i{MA>_&%s~SLdrl@c&Q@D-LdgI&J|Jd18^TF?rmdvZL8n zImTN<+eH*aK`ZTXjW-h=K?kg<;&rr9#e@ZGUG}x8G+0|Hc@i_LY&eB0;O*^b@}qwT z5_*kyFxkfXv^p6$9-P=c?C7v{GsM^SWgCheXnV2p!C@XT+v25se@eLz!Q@K0?^^>zaIe=BYA%3jQ7~-8vS<+(SeN^M1eqleKgk72}L!5 zGzkq~pcR5ieNj#v7Nw{G zxzj%v*Ghs=CcpGN(4mI2{PEbc=iNiewtjC)pn+2ju{rR&icRcarfYEsM1K<%5D^CN z$5tRW%oap9xd{yflRBz~QrLKubt~}vtw&bY(Fo0IuZ~Y~Gz(QT&P%D8^E;E!(+F@7 zd8}5uaRlebl3oFHM{x<-A>hGI_`Ox@E2ui;mM1}^Wd?T^qWPZ;1Rnl;k#o+%H8%C* zNQayyx#(YPv=`ty?2_H8g?@DChJFy61*lLn-@oF5gqdgP63AyY7RyJ$ZSF`i&1%h7 z#3w5)x=xfKHv4?v4D3J3j71-9gR^sHyrXJ;{@SDC2OC6MyOy|KI%(p$n7YJw$d1-^ zh)wp1`){FLS7XK%&q`EAD&InfXhV)5_zB2Ey(*Vqv0$3wC-3ErM*fFY^}*O-qm4#M|c1clFSzOwWq!zq~ROP8B>$(Sh zWl@AcFEAd18p$z7MV!?YU}fur z%7(y%;NXMQ%n}IIrGkMa6u5+Wf-K4j`Oqx%&<`4 zGd4l^Lbi|!Kn@Irl?k72N28lO3${uew@QM!gx0C zS4V!l5Gh@oblMk&4&kV){6SE z@$t`gc@ol@v@cJWc5Fb>%`cId&gpw%8Lc34-QU`2S2@nkxS)2_J$O$2{F!apQhCZF zc}u6xiIsL8Q-avg1TJOVzp>_Vv*7Y{Er3i-+dB4fqs1fXKZW8YXr5aaxKJktOY&sb zu}`>3+nTTI66>7hQLez8S0b7(>l%a#i#{_?ZSfxm%}rSd>@3B&0~8Eho8mdTBEU@k z2O8q;&BL$$FM7cb#><9&zWiTmD|`O_QKRcepPpEOa=0XKUM2X5nO)Y=nNsBWUg1N# z9IUskpy~KTbyR84qSWaB@xgVKpmtt2b~bo?3EUIlYW9edjYnU8V=NVTsy@0hT(%xD zvwWF3`K|kpK7jgC)leq6zVW}C-2=V#qEJA<%a4*Ab%f z)IxJv=o2V!9e+^fzLdCb{jUZP_Lc+%|Mv~R_HPD|^WQXpscs9`eQri$S2dw5EXgc? zGNhZa{qt_pO`u#@;a5v6D%9HN>~4F7-j!EBp{pdF$MaTm)eP6n*r^aYJ|O9DnjRSio#Y?NGpG)kSWfn@MhzVehm? zOe|-$L}I|92fEdeDH3${N~hPn5rK8MSVTU6;%ZuznoMp4evGQ}s09bxqx2$lh(?(S zD>WSNXu69%%F1a18>>Wev=Caqw!%-#q&4a<&!8In&Gh~EPhYzjlDnq@oumS4LA%XaTiIkRy`19%K z##C;b+P*XcHx;?oZ1=_W!X8LTzg_lr_ekU^Npy ztX>~P$&PO=i_L5~z@^5rIc=JVpyZ%>0a`#rw!~8P)*JiD=72t89zB-*q(PrcK=yJK zxeAg3phO-vlsDO+4V?Ak6Pb@6x z_2H3`S@2i}fJ7T@73$!}O?ORi)J;Np`IhZBCD(5^4p;EvuPuZ@+^S~Z%`189oVlS& zVjtK1uk5{lOk-)j=BHJ(Niof+%Rto_%EG}1xX=cf1IAa*B1kA>GyG5o>UO&rED$!f zpbfItmNw&&2c(3i4fqF#GWNmNrn5yAqgAQRLaEzw;sI@yu@CmKR#c_OMVACg_wmua z>U*W>rV=a+!U* z$JogI^;4j4I{f4e#8`&dh&TVi;-PgKZjKX|+!brUJ4JNsD)^Y~PhY1Tk*k^kB-&!U0PuLG=hyY&$L;}OO^ zwN%h^a^4i3sJoH)kt_gz)GJUk{aTIBJ5i4_dzZGdqL*!s+`f0P^xkygub-|k-7j5U zz{CfhBs&UossXm!(Qb(gpCdy}WN{lz{ZWbO_3+Z-6P9>lI5nL!9oY1e9xrJcB>eO1 z@l=f>pS^Rw!hqfVG$cnv2EGuIXYR-bf^wa^qV_DiUtV{exY%wOp*Bs$JIyy?C1JV0 zeC7t7Dr?GYn{FaQjI=pg`drjHU3i8k;x?Lf`&8QX#2pat;W=`n4moMlAF1>#`J$4t zjxgJ%g1BaA|Myn~9eYOhjF( zl&8Vlvv)1JN! zo9=wHN6-v9a(BXB_=tOk zR%44E?8_rz4(T(<&BH`4d%)G5FlTLE*LLpApx6_Et*NUoxMyBWmfnNk*T_8#8;^p7 zabXJm6zOo6K<4fc7r|Y1@S(3o>qB;ZW_zFayD2Hc)kWIGsVV8MeI>@AE$Ki1oy_n0 zu|sJzQp9p?>1TE?|0X#erHP;c2c4eK`dsW>&=9XHy_bhzvW=Lf{T}$GkfR6nU+id$ zYe~Cun3y&@l=+SMq?-a477xd7KRkjdYgeoe?$P@Nw>W})Rw4b$;~H_CxMPJmUsI4+ zr*@GzVJ|!@5k@ZUMoU>_6U2yjQ)*QOJ!?|^njxlNqE>&%G1JFNqZ~^a=F=5ws#HlQ9~(% z-TNk%Q49Q6ddrHikLOC^1C`G4Yg+3OpCikUSgo)Xo-M#-N15i=(PMBQAOl0b+?Hd~ zO1-AWif#-8RRiE zvL_M1wK+|gqEoBtRkgN;GEr`CIU_9%`((~a_2bfUkN|ME$F5AfG)Dpf2?+U7Bzib; z{uz>A+}zIuD!0d_4G$7+`L|?V=zaolak+_?uZz?u10|;D93sAEl4X5emz4UpARZk& z{gSJ*dnFCmz_Ep;CcGMOC!0aNmzNlkV4Y>Z%y!@&=a$*q5IHnmB9>3b8vPAA<#irp zWQ2zMc-qhCmo#i2B6C$dwcnC=Z5xUr$}rq}cpfOz*7Z=~7ypd^+0iOORZt$f21r?2 z)*LjkcX9`{ogVsn$ix%&UrTl?TH|5E{U}9n%S`ul(d3Z~cL5fM935Me6dIz}6aNLG z`FQb|#y22S3G!u5p~e2dVjWqn2;qBJQ} z)Ud%xbK9oq%++rU_}1jh2qnuAvW>yP^GIu&0fKm%IEm0{rBgZmr_dqB1PmQ*D3Us; z2>N>1k|X=6w$7BhQ(Ezzotu6hxAazR`I+viCpASHk778dFJTWYb`S0N#UP{hb1lbJ z$QCy)nXhhwMC}0rS9b)Siqw4O^TDe#10rg8TdunZT5;Ndd|}z4dnNGe9aMrWaos(h zSaXg%(iQLAL_~oH9_Nv6A$6>i+pZ8{&TP2)W(N{%?iB zdQ(@D_5Wq{%MHUsJ*nl$=>qNNGItPimO}eyUsS}#?iiTu{rWS8>_@<8`se=(PnC2D zJN!!i%?>;;Tk=LokW&IA*E_<$xO1AGjCP=k&I0039p5Q_kofU@J+kC}{qvIlAv_dU zBC=E8EBstu5+&XUDE?RTS~m#QXc%&2O8e#qH?^o1Std z3O_O(seWxhv0IuXr%&E)@*+x}-w0j15nI4XMTDb3_+wjs(Vv&;|I0E_A=Hq?JA9qv z#P#vK&rme+`Ht(??|hR>+(K(EWgsDsp18%1Tpq~}(NF`mteh5==xL{iGjX<;<-aTF zqRj9A&>!sKb*yxT{Vj4v?B@FH@9hJg71TheB=8`Ha>O!Vm(ObL9)qb=XEz7rrF^7x zT<1)d^KZM6yTkv2jFjDQX->~Xk;9{>d8WJ1vh?F(->*9^*o_W*C60okT&72MaNf08 z?h%l$Y1)AgXe@H3yY1j04&r5E9bG}9f7HR!eRt>Y{5`WcST>D2fz{%&(-HotGw58kMxS$yc`MKYrui`DMEDJHqi4-)V?GoX#ouzEWpf#G?#0e9ymI=b&k;Z=GY!mVAoM0bc}hlri-vvl8gkAXCHiLgPl?iRv>_ zWs#^^IH1y=c-VPL`T;DSrDs-2KPP%QSTC}(({a1fUmE<*T=Cue2z!{nkge~8Dozz@ zV?ENDM(c5&es}Vd88p}=$g~Gvwh{pyL-AB!r*O;BmS(ooRWlba22Y1G<*kNN6Yo~M zP)Z*>mWV{-ncIlh@K;nf$g-5~$~_?HG&BS6ZWsR%-<0mT>-11FtF@_~EnOwy)G*|7 zA#;Z2$w{aw*$49e6X!;YWvaRHm~%T3A=T~lY|7I=nO;%bPwd=Vgiu(!8uhfiPD>xU zOo-z+-f$`ToF*Tic0WOO&Qd|2EY35wZUn<=*JD?Cu-ho@76uvu@17`iY`T~ zy;9x3UJI^s^NXKU-#1oVi%(6_?~UhWI;0MwyOH~HMIG+o7yrCq|7i>MckhtF0y2|x z(xP;@{(cbTWv_*{@;`FmQ72@)`Qk;=;D8Fa89PgyFfkmaeUHC0tHbirtu@22xcIhH z*owU=Q!(d`Yr{~jRXKk;WglHDnb|p3ge;hnh?MygBiu(&DWUxhqAn=R&1%}v-r3RB zzx>{=sVDts9=x3g<(qTzP(sO{luTEqQxmf#H%8AY9XSNb+^TkJk_mQ_#&gY!MS?0E z17SPe!RwSqZ8}Gjw1Ki#n3g8BQ^H-GxGKdX8)bdLG%HSrqpjqzrxsDb!j>E@4UP(} z8FFU~1n_&OcZvrRug87ju`7#=Nj&Xi2%Zd6@*oj}?d;@Xkg$KOTxOTsoRt%HbVI{4 z(H%L9ecJD)L(X@q9l0+hME`{r2KGx{oGQChp#o%VqS4+y!`iaU@P)CcyWq`2yM0uA z&NVqtyPNnBxl8H>k@BTW0-W7RJyX}=KO=#62aDEV7hr811Xx~XQobp-xfXw#|5>CTfKq`!f<`{&Q|k7|8_ z3Wvslx`%iiaA_QxE=An3bDc54xZ#y-P$tcM&~TQl%5_-gUnJVoJ()v_$fE@%eO>vy zt$_?nk-t4#fxb#nJdDZZQ3rb`f^6ibs%HNj-=u%hRubs%%n?)jQZ> znSv?|J(rxVh}X2Ur(d?6(Yu3fIl{SfG{~JLy5n_!{!IU9*D0)k6k@sAsb)QNh*dn- zJbIQm5q=}dy>%N#m53Kl8cc)rP9<`g!<0_>iaC(v;QbakPtTyq(&;LDWl1$%Vzfp&UOVbb zmeRo)zdO&rZR5ooNgIBHR=9S3c)NcF#$Vy$LFYBnwFcNtFTls0k`c;>S>OYfis}bd)75 z6H&x;nQ=4d6;%1H5L$*Q<_^#7E<{Ub%HrZ(hi4$HLmWtbZtiYTRY%d&vhk#HIms#< zN*zsv3yY6%sYkS^N2|FA&n&f#;dDlt29Qq+7o_g za(kq}j9sikj$G$H3RC`q=y@A@*y+PSsY!|iwbLw(P%`dEP`_09@?5y2+EAxtIb@v^ zev5L$dCxn@DnzZuaG#M6sS#Ww$_ZZ}Jfs`(qFO>`%XRbxThb7`Wqw1EvQV*RKTo`D zbFN5C2n<`B!Su2Kk%LM@uzW3caxJ#|zT4FU_HM{lf_oxpt*1;AxV-rjxz9N>yf`Eh zATda7T~g*;krRoD;wdX~PP@WLn$$M(A$JX#laj@(BzJxGPyY+|Vq-;B^7aqck$L3*I8K3#6AU@PgrIaxCnHXKzh>P-& z!+exG6BlEoGyYjdwwN}p;kg0UHlcNt94HRp;GiSY=m-6ie0?wlyTipvt1 zAZ@nXc5@gbm6(bw+0Gd%M8&prj;HghZD|Y92PtAMGr1E&b9$u9)0PE6g}Su2rG-I9 z6MMkMB=>_%mWE~2URfG^z=~23BH5U_&UB^1^dqD$E4y3^?KzA_Dmqar_COu9vfZS= z!!IKH_>4)ZjmG!XGANzZP;=?RXYp5>E=1`q47Faj-$Rc|_)wUODs!7)%W&zIykyiV zFLvsBSLatL7&I~8IO!`qgdV=44wg_URyk{5MqZ1msC0vdzo@AWX>cG zZ%F}9+i*3%T<4s;SjCW;-5YP_MLd#c(*~yp!-{#Oj;rJ6Ufe)F65aan9EPbe09!aV z6Uf`V7^=^0@6{dx+fx=7!n>_D^AyjdJ1{=l25$J75G&#r| z7+P~W&0V;8<$m`dtGl{`ra|!wb=Xf!ou|Bxo>@Azdpb6HFsCPzc5!oI;)Q_{kJlIP z&Txx_uFbZw#=mK6fM}+RB7{9w_-y{_?p|Do^#o%)lNFzr6~ky@Fnu!5;8I2Dto$%K zkvU7W4_6mE^-05P=CZ%v2j_FI><|t2NvB3*>=pJm5g!!Gow<@YUsZiCDvQHG)5-)# zW)IMZZ_vG#i1_v7CIU@w5w?43qjPdZnYL7*^#X66-jiszjTU1ax$Wb^L84nXm16ki ziO1!Knk&(aMi^%NeA=EH_wkDpVLF7cpRo4oZk4@FKLm011pV*~A*(7<%OCgfZ$P%< z30^mKSQ1(2=|c&@%KQ^PFO#lRhk190zHv2>P7H{hZy8bctr1G;FhkgL)U&veTXb5( z#~;r}%kBQcXL!K1s_Sf3AuSX9w}`E2baDEE)}UQ!%f7;G_YRp?3b9Q^TH@UCVn)^! zmfPEmv+?G~2yD{TXPFO>X@x$~v{9ADrEKyE&Ch9Gw88$+ar}g~IA`N2+BC&4uJj1l zZgZlN9z`(w)}Bin2>zW3UtxXc8f_Q+RPK7sAadSBRt}q3LdZ&HIFn2RueWv^Hp!VV>H~9e4}_$Rk@?D>Ydho`UXZV13@FZDg^a zw~E8-^FdwE>OoWILdy_+FAT3G)N*$^kxaDbVFfZCZ5AFz>5^2ScjGeM)u+rIDh8U= zp)%DYJ7z`N-ezHo|BTw*c{Vs7HZwR{%-EVZGXLj=`HwHmv?b0{3AULNJ$gpkMyD)Y zBj;kGI2Rs0XJc>(v}l*wYz=3a>c5S)Nx@sdo!_5;Qk^|$^}9Gc3+coY zN^ZmE+Ny_pB3EU^*+a>X8#Cwb4U%4n;?ao3ZhFQJ11P%#udRugszcr7U3eo8<{V*!RAxq}9 zI45$3vl_=}6h`d2qTBfdqqyl?k8I9KZk^sl$zdyoxR0V@O1^c>)y?-BqwwZcAzEI3 z2rZRPxsnUXHha#FdSDRg=%3ZXVP?x=4purgGT+M+dG4b>f1H0btCI{78)CqL_^?bL z1bL%}pIa4^!tRY4Y69rP;U!ayVR!A;qfo%u-ydaK_R9Z!(FY$-?ZDEGzf}-d<3K|+ zdy4VT^lPGPd^qNW!tzskCTf82*ga5B;VRMRCi{%!^~}X%B|j8x&@a_K&~PoqtqvQq zk<}^qKmFw`=c!D)?)1d>Zk!i!84kM!t*6{#i^$V&Q0*B^*uC;|vKL$vk~}x%o$Ia3 zh6wS38PihJylEXCX@pBVv%92VqHj~y$8mnK{^j6Ql?8fweT8F`(mkO|g?Ejt{~^0f z$6CDcEi1D9L%GiL$p%0VFN8TyS=Ry1PAZoQNmHr2Dk+@rw^6f*WD7I7p;cb?UOpUKsRK#Vww2=N>E%QjsH6?TJ9%CADgE$iF zsU!MUD@5_;u=Rq6M%grjDW3w>k*{%T)_10_{!8@3Ps*P-H(t^5_$ufdk3*xU)+c!D9?n9>>oIQeb zEBbu~@^MWGhPd4AwGJB$7RC06e<4ipKU*K9abF%hXYTAgOB9-TQ+?}j>j^z)9Gnt@ zws;B?0pkGh$(=t{8!7AhU}V6&$j6F+#}+6lb&5 z!oFcbh9R3)KnM!J8y4qq9XmA;j~6a5v;q>>*BhIJ-qSL$1P5c6Mh1z)3Td4uB0K~( zS=AnHtH62=@bB52Yb9Ej86P5>!{V(_0+t05F{_wQEeuVK`yO&(+4xdGcDUd(li&FL z@P(?i>aZ?IvSN$LCqp8#;sKYz*bDsGWw}0h>yh=rIx2LGp{0AmR^wD>-rW)bbMppZ z_PvaQVPc4q4sdTOsawUnd|emN0nDD-7=7wqX_XCla0*cI%;c1@m!}Q`J4*ql3Qsj28*;LR^WCR2Yh2{D&{ zr@9mbTIS74tBpuuTu=DIfL63DV%5AbJVyg?D^vsBiHYGmvZ{D-=L-XogZ=q?0YCHs zyWN?r#JoDv#St}JpB?jP&Xu@E?$KjLa3ATUXwQNRQ}N(J>&YFEg~d2e z%QdRJ8$J`v06=TDuW*zNM9cgFOU|yuwc)1*^1A|l=pHRMyK6B#&uD!>0rmuU#cNn< zprpv%B)Dr2*)hxy7UuAWuLN|DTVHCTblOtW>RdCJxD1I7f1vLihEIyf7(|_(M8RCY z?8y;_kmsj;-U?AQuo@A?2sat@(x&RYgcp(WrVOqpq~5&{6;(KLdj>TgZSK}7aS6oc z{yA-8ZaKWl;kfbZEPm)HMxbtY%{nS6ufz+7bDNH=GQS3cls0PDZg8&%v+amy&hUp< zwE8s-?aC0H#z6U#oJh+tF{L)M z`Azl%yVtmM`bIMteFM8+Q69zkq&pDTx>ouJJ4 z%!AQV`%LDBc>WK3)}@VBJWA@YC7_#4wv_(qCn^Q&yq<2CBKoK3YGfdYae+HwQ!VD_Need?zTE`_%B67~ zW5vF`+JdN-9J-outccL7sw2$IcZK&b)3D6^L*MFg?#u&-am%jZkq!x7wx@tsvb!rv zaBB-92m1looo=s~&}ZIm>FReYG5>t}0oq&acjo9$NRwplnCL@l<|1ecJca72U2)27 zg zRpGLsGwRvuJ5k&#enJ}?Re4V$S&9A*8u!Jm zH za3dkR;47wk%NlJ`&CFI?a7@Mz*X&$|Xw+g1$ld8uzX(-D#HN5Br^6i_e6(=aJSBUI zNsSgD5V`li%cjZ=d*ZcRH=!W!`|$i?-=UMR*NkokpAo>Dv#2#PVdL%9eBAQinxFQbB$5O!-QKW{)4U{>{O}$h7^W zCuK3!qlYt_On4M&L>*x8&)&2i=&+fpBQkJAfkI1s%$lxlH?K};)$kiZ-3OgX969jB zRWCs|j%&XI-%nJeqY&W= zrg@*33jOZsTGMfp-ymvm?iij@s5yNuO<-4fz?py8lJlhMUXdXc1i_nKD0@_qO;bQV z_GHC*FY*Qz5>O&x{GRg;m-%cA;H!|FP;I zuH~C_a0YKJ?L5<99_V41&~i<`oHw4@V5-mz!ss>s1Xn3S3P{3e8$@po7o%LN2el64 zcGga#F3UB;ygpRZI&+a~Rsk6g7~ON{J3Hxe!${?G4_=1!=B^~3a+4}ktz&U@&ADLr zehtbbOf!0qtO_spE~X0YZGeu$h40!BR70A-4Y_mOov`CY>Co@8h9*F}(yt_>Oz}6v z)x%4}^>JCh&>27aH%dXQ24W@m7NhP*;x0YyZYi{@^y{Y|s9>FLG1DzJ({pBWI=W_0 z^6t^nV5{A8tGe(j6d=HX^NWGlAB6|?XCb#+*5Ei{h>=Hd%{vO2JH`9<+~=pm!MR?$ zW;(Nd;Dq%q=DK$R2jfw0`?BhnrD66?6DBxk*ds}amLe7fV!<$s%BE=PO#%YWH!$0M zyyhOiFgl*n2J-`ZVSRjcO;D*e62C8mTXZ*Mc7H&Yc6W>|Wj^pVgVKGWb-q>f6-2*W zbGKA=>S23SI@vp&Aaap?jd?>PF}ubSy5BuD)S{(5xs;8Kv9J!lHI&-VLdQwNSig^w zrv5eyPxs6c@bkHYKQGV!+w$ZSnv}RR)69LwVCh~NPCuWhFU5A!fVdZzcVe3<$vwjF zlv@I^o>gb|a}1rK`XhAHzN188y}=ggF`A-oN+!xt={GKx3X$Lpw);W$kJ{NUF%o9} zLza9j*<}uPqyqKraNDH}S6EZ8@P>s3cwi_lMiX47hd1|jDkyRlqyVSf9@yMf>g|g` z-PIztC-7yb`SvFmvtmpa>$Qb;|ICGbkqejTP>DGU?J8_>BLle@6hh;ZL5%}5BT@%v z9^t-nWic{LJ(D=Pm)D#-f3$mA01H~+KXGwCRApn(XiK!eM^(I~*0$wc5xD{;dgqRx z9iH_x>@M_UqNIsy$!!}V1!_6s^o$}M1J1R1G;^Vp7`4uxJBLW{-0y_YuRrj1jUV+ny_a!I7rAdMd-g?524p;PD2 z_4NBh+3rg|GaO}a7P(keYhqd=n=zTLLOtYY%3GY=rKKMCx!kq+qQB08cp2R9#x4Xg z-;Q45&dsNn{8Ie`Ky#~!MZxWjqlKeU>gr9H(V9xZNgM0IMLLek0267Ub4A*1_sG(g zB}Nxj9Jnx@Nc&5Ssms4p05q8iV;7IhlOX*$-&W!*Z6KBz z-O@(6rtq2aQlL?hUs)?1E6$CcR5>e&hnxB^0YvHCkRIJ9ftF@f!B+f4&A-1^ifzgL z3?VHt!%Oeoq#w2BL!SzuN1Km+UzkLHG!ER9m0K*LYH-!WEyzZZybSFo21+qj7Z<*$ zMg4qVd$s8=-z_0pZT_f0qHrwd#ZX&dOD=^_5)CggX$j%K4VRQ?qjhX#A#=&U>tH{v zD%zU#^%Ul%s8IQ@aDgyAh+?ZU;riqHHXC!n@99N53R459KT3cDYJFbyD$*Y|1ltan zi;F;sU#xn{4boD(@E{O-<;t2+N|*LY7XETUTvRx?kAme*zgk=0V8`|S?t{nlo4|2S z%F}&K{N+%z2SdRup(y=p)Vb+B!}L-nEd)cQ--K*Pz~Jj5;F@Angw#96g^{`9K%FDp zEnIejI{Vf_!xvFoy5B2Ia=&V^JuP8m0rKdyi6+=dj^r`B@MRmGNr$JWpQEiV=V?|_ zWV(-W>M)m8Ti2hKFNbc1AILO6i6OyGcn&^+i~iG9_)djiVU&T6jYn^k)Dv!uJ=lR; z=xXbljt^bkZN9$mbJkSjR^l%!VaF_P9c!ipl^>x|SHF5y{$G=>6&E|%>f``y=famL zTsT|B>Gk_pwx^J(aP=y-=R1~nFrD%;F~Yom-^bi3y-sZ7W4G!rRQ9A%XyMEZ4pKX8~A9({PjkTS*GB z74G^ku4CiA1dCx;^kV2^mdKZ9rkc55HJ}w+p1;VTt_;X;1X9vj<5=Ly{N^#&XNhhd ze|&nqX{tX-;x<9_<>19la0)7vfYCogUH@xkUpFajkQP52s)rXGwviYrmC|ItaQ*0d zvQ>vS9hB5;H#-ft#Z`x*T6n!l*axnZw!*FAhnejdFeY>lT3$uk%1jH=F8%4{3rwz( ze&Y=hmim;>+%ZiFAy{U4WXUkse--{TO#3&nYd0NnVI7oy;42+C@3Y;ym2$&niYKl+ zA}|UAq4aL4{Zgh-XGjoHcw64R6;xbF-%MHhcNv=tUnp1dLUi>=bFZ^oLdUZ$|FJyi`6BB~CX@M5nFin--+F6^E zBXvs=LXbMS$z-AtJsVdfG%>`893MNg$4K!=iyMGNPnA20Th{th8!WNlm40e(&r3wY zCoUxTm0vFSN`90|eO%17k>GF|=$7c?(Xwz*uCeE)Ra(3UXNJ;;7XoG*%I$reymNWd zS8B(J#n>F0E5h&EphFnN!jwqoa0Lpp4qFLOc0r7er7=TGG4hckO6|Jn42J3-LCQ)&V$@U0~QsqyMr zt7Gx*0}j8CdRA_5Y%eqKH4UOwDM^ZfybrOO0~x z6F`%Pb!@DByEr|%V||X!yBgHl!n-@q%J-Lkbb1|3pi9QPr3h`caeMl)P~{HJdfOkZsD$78L?j2+p$2LB8w@h z6$6gK1=(^P?w4Qo7>^2{(AOVj43U!>CpskDd&`WWAQc`AUd6a3$oaZ*f0P}*O9872 zRE^}Nr@U#eaTFGd?LZq`2YE({_c!dVDBT@}mEiq&CT1X(hn6CKNnEAB!6wHxcj03T zT5;iQSe{U&6O9}&w6oQv*XWH!c~P;w52No7_^;Z~@%I6ATe{D`cfI*(zi`~7sGW(b zzy68c3{e9crxcW3v4#5tO>(&{Vwv)eqn@(&0&rJg)w^jr3b=W7sq@a zM>d8x@v#I%lo-ZB;2+8!s`;jdJnFOv^Qrb|Bx=BWItkda?&^!v;fp12TnP4gl|sSAmkb2fWqZcJNxE4df1Do(P72>fRbZy_@&D(z!H=&58Z>6+wY zAURgxS#&H$-3-~N005i=quqVZSmIg^_XDo!6|5oTrM(+Zj}I|kx#JVs5KonZvGHCl zuI-OPi9gmk3hm2v^IUr#$$>8nPB~O|=%Auz-ZovlJ2qkp?|Ot72**u)jE<||%H!*@ z+cB9ieQYlbsUN#bU$8bD%2hWH)>j4*Wx_1LLX_FIUfq69& zTn+&uyu3kz1Yc^mJ#~2`rMJxr=fCgfGm}?vvQ9>GV@Dsyg%3yIGg%>t5%%)vW;!gT z)YTSjjs?`hokV-fn{*lWG~beCohIdV2i{?r-VOP@$Qx9pOlst{_Q{c z7m}YMPQZ2a>ubvV?f)ohd>a1GIZR>&q%+Z;^$O>JufwvkR@&cpYHD87e9LYbUpl_E zc1v`W;~9$O73-ttQ#awx;g$%WbH2Ewj92B7Oz zPk5J>#>0@EbyEd5zr0a;;nD;RGfNBg10!rKvE?Vh$rf7u>qoh_3r0w2)_#poEHh8x9vMk*7Nn(a(7|RgafVE(WYU$zj5wMDT23^+ z3Pz1SdSm(XkGA!_;D{#e1UFXyG7wiC3qT=A3*9F@@B=ymGr{~yO%YKo+Gx*Xc<~U2 z;gJj+G1s@XFM8I?$hb3K-A*O;L`7zrKhqrLnpev$$IuAf4deW_zjT1%l`Co*7_~ny z$^T*S44`5WJIb(6KH*l)JL*$`-!0` zQ*N?*N`nDferf5FI@mrfN%jlx0eNHeiP0SyOCqd6c|CZpX-!FXp(a^M+xY;v`}M)oJC5y^Vu&GmV$2wIMRG2dycVA%Z=F7awCGkF(dj}> z;t{_t69_;NJ3O6wH(k(A!QzRV=nw`w1DKa83^@nvcg`hQqiWgiC1sJ#mOZrqD9r*E z2Xay7V9o9|?A(-6k^-vPfyaPPR^4C9S&eM0gh6gAg4Z&LmaC(_k;|I&><5q8X<3@~ zYu4hzA||@iWm8Qx(YOy%d;{-pzzpiZ2*TEi{=jI!$dVaDgE2VmgtZd)^wt7C7ABRj zUUyDEn02&)_j%f#axe5h#z4Ie+7Mnp1kHdZg76FTO{rrCu!z62X0$I3RhGK<*T_ql zLQPTR!^i=HpVRpE-2nrsU+Y|6lbCv|no!-HvuR1uKQ`joOyERap@~%>;A_Zf+=eFE z^a8dl*_J#HFE|fv58|W8ThXWhXQ` zp9l#STB;D%V0OSC8#%1#y0jo2gS!Kc5;jNFae1ufzs0|X z6OR{syp6;*%YWqt8zZs{?V7AQnD=#p8;d#Gd(Y{cmh;}=0%)k?9%{}Q-I8m&RN2jC zt{d)gVNP~6`v79zNlm=Up4)BpzoNl65^rD;B@M%{7WEt%3UVIz8p%v%Z-PahB_%fc z%r#WrU1I`B1cAMo-9bc21OSk6E@YZnR|M%DkMsdtt=oB%Ds8wYf+ZY>L$O6GQ{~1| zDMSXcR@2>T7?fYstmRS*{p)1aDEoo^WG-c?75oWpRC2$5H2LX5#tqMfOy3`MDhp$E zU8;T9+p&nm>>9L4=x44I5J+fZly72KsZu_h}w2VFoS%E49FMl{O3oHCuedvoMb#^s`M}p{T#<;()ngv+z5Vl^p+OT5y0053 zZ%JfR6q!MWXZfROz}t3x2H+dA(s=j4RBdiQxd)?hOGEC`hyjQ=Z?b!vRETy9{Q za&&`~okN4g*qg!U5M^nDY0axquVYMU{6(dyzHmU^Q-`0TO0PyEYQ?D`0YK6#wSuSw zABD&0dxHco7EFM?pwaGa9w88lf>L&`ZFGvvTL|x(q0mBT8PS*uTTE04=kWC)Him`@ zk7&+{+Jo8@b8x!PBdb-A<|sMRq86^Ct7}isaZT;k)oNKSKgk||gv!-z*>HdCytGu1 z!_m>&1B{jGM`{ab>O6X8nAf)D(B?B{z-(U7o*PcniE$Q(q~xUBl4fE_E!;RGUAB#xd25K*^CTmNd>14KI^OJFkj#8oAGVw6F-1 zsOXIIXrDlDTNOR0N_QP?q`f`9%1!#{XKZYupWU)P(1O&=-+6%X^wvDsW?|8A5nsqnU0en+b_e3~L8jxNM}>#VBX+j?MX1q6fJ}IZ{uK$;Z3flq(TQZ(1HfoQDM;|| z6&HiIpTp}SigX-0PXd^E$aDjwXGy+72x!Y55>S#B>_aYAj^_>-Al46D)bIjaIWGgF z?#fdfJS2jSw5nY9{U{)9M}Wm|if4ir)Q`Gab9J*c-dRWL+O(>w&XG2M4i6QfoywY$ ze9OHDtF_HR3!!7bl$gcjy&W5wt|CD296Tzx;~7_^>4p$&PBwcMrbshJRqUftuy_xY z7+Y9xg=ymeL8~AU4Us4=95uyV32|9<56+wpbG7WgK&D+DU=FEKM{ZNjXDHhPmTc70 z0QusYqCHWA42XhUBK zx*Nul_s%^oPpjp51q>f&HPb#r#DrLO6N9&HN6*MQlZ>c&6%5SrB{QPTs{mi~_NW1@^%H-mS-;DMyC;HVPOX&=^sU=;z#3ghJDEl<{-+6RrJYY;`&j<;>|YO#-hBUgkNUcFJL{-o+k5=v(#tM@O1jU{5h9 zf%Ba|07|GHvuviJ_$xLhri{Vc>Y+fxs8XMrTW_5S+hJ?i>sb#7;!*lmX-w5HjQTcb z(m4M1-SeLQk=LPJjk^N%)IyX5_(q=UK=>wBaLngj5GVw2_6@ChJ-Kb~L}tIu-=SlV z;cu~2{}Nl?j~s!P>~>4lE?TJ(FJAA6HJp~o2$;PfUi~S4yZI6=I=%P-c8Q}Ccg^z^ zX6$k0{Ns41_Q6QSYlyHWRblK&AU*d&!}t#?CU9Y}))QgVGQ*3E;UnaXjkq-c7<((w zQp2N%%u~Zlk9}eGk>4XbD2)v*%q~P1SiC;Y6}9ndyCJTM{57CwdU{tg5!Fy)^a#IN z80m#7{JQ6^s1|@z5}r5sV;59t!;9lD=<%qs*sgs-H)%jW9<0u7lVRf!jr3RP^H#s? zX^*zLxqm4z%8aAUP!yj@{{4NG|Mu(O6E_-pbV@DZMK#O~n2QlRQyZA5X6Zk~j8k)t@q-6+jTi@HDAgWS+WswG#J>v~a2 zpB^7E-G0}T9^orm$Ka98?e_E|Vpl6rqtU|(bq1)wct(^$+;_wj*Ea4qKW6t`BNaEb zm&Dm#XeoY`+{2O|KzK;sCXbQ{?TY$wAm>L;xOBIqYvgQ%hQp<0Q2qG--zO?wm;U?z zEV;M!PdAMJ)%kTe{=nR4>@Vv6XQ)OWC$@LRr^Bi^1lbm_>G}6C{T3Kq@S&rGyG^Im zxl;v9(V<#+m>J^_)JKma zFEXePn<558ZwF@Jmf0pe>P+*^1mIx(sAdOoWG8_bJ|_QArpk(B{33b6nmn)A_d)zZ zV#U%AvOHYHP<_45cg~rc$vRtu?dI#;`j38{etey}sXd^A5X{;Wve#cFCcH3h?yg!>}z>xl86hP&QnVMgWWYFlgW;AL{s?Y z^C&Pk4HKTgP}V^@?N&cFoD#y83&Tk0$`_hI^@K@2hmkf~H;qANV1yEEnm#Eb?G0ki zu_UKkPXIu^hZ$kH4)U)GcDPCe>s^Gc+NJC`H`!{-z1>O!z5J)o)1`R8P6luQYL{s1 z)wq^E=3-9GV`}flKJs=mW@tbod=+!CG?Cfvktt5o9r_FWt!Tye&-My$Z|2#Yzk3Q- z4?s&O6sNtIE%DpMWOdKaxz-ZZ~5q4zBGfM+4uyQ;p<$cZ>7%yMbsGPYQoH(Z2{8 zL?U58bG6BA>(<=%+)qwIP}>%L?)^Z_@XPu7xDjlNPO0f`@jd{NHTFN+mC{z>!zUUV zljTu~*?}Vwwn_wZVBWa4)ILnvo+i5Ct&GU|TW}Tla)}YrHijd(t~3)Z4cE?GZC&zz z_f7r&yO2ajruItN4&bvg*+gzQL;LB_w8jHe-5Qj}#y)f$ z7+v|s+Ebn{ej&+1Zp*vmX>=*>QdaRq6#Zx6!+M9NT<54M-8E+TzxxTqZ1vGAwmaWx ztR!s>uoFi;Mk9l8kyNLD27+(o+iVNO5PrR|xD-JYZf@IJUz)5hzD#Tzh0feGj2zv1 zOgB!AV}2-6mSJf2{s@?UveLf-0=@U^kX+x(zaNPe9tP!p*l^WL@yxr>#h0_&{N(B9 zr7vL02AXVL4V?s7_;-u+WwX9557Po{K>tciE`NoHlku-y-ao6OaMJw!-&? z_xfO=EVt{!wx&G)7Pe+A>U%uoD!-(ukA;$MI52eioh5s6)0)}ynCo9k;DgGC#;%k` zux!mcRx}N_2^V!;2jVl0ODG0oP_rr4@a1%`IwO}=<0+U(R;Jf6JA1ntul3+k3${i7 z_h|b2ORr&i3>Gr*Hht)ujtVzM#Y_9s(uelNqeYbtGF8;*BNZiPOnBMD$4>8LyWyTt z=$2Zv?58ROGyie`Vt*Nq-I)tKD-r9H74|r{%e?DkH%o{)FTZpr1_1OiI8O;XB6Su1 z&gH?Yj#T2ugtYXj&<^l*$BpHY`$VyPFU!}#=m+3a?ToC^Z!kwYXHCARHK*M&ZV^Kr z^uvBQ=zF&0EG{qX+fC6lh?r`avR}}kDvPQj9k$|qfYrwP`AdF``=rOhl;83XuVyqC zGAX#h2?#TYa1QBq`WlzTri)d|Uw*#?;Z29JSeagU8M>bFd2eV8(9!XgaO7EK>x~*r z7R67US)20-Hq9w5|CK8_c(rNdMR)~;X=J}UBU1Zfl=?q-d;g#|?>x`*s=lGok-7hPtrAQsndyeSaWY?Zujo0uI_H9=k9N+ z;Nki4{eHfm&-?v)=hmEv9NdN^(MW$GilpD-r7vItfyE4`3qXPEW5=BACNm?lnv!>8 z7`I_%f&leCd;Rz=3}qvj!&Tj z5Hs}p5;AM@t#Wlq4=P>vxByHDm<`^7pRsYhrU4_|Jw>)P`WDqN$!m~&ud+i;d*yUA zm|yDv@Yp@Y`o&ZV4C?iQ%v*-!TZmOjRkn1~Wu$vBv(|q}GHN44JL*ERR#Yq$k+lPP zYO+HhD;`n`3dtRjtFNs^ou_`%KXFSfxW;rBg*!EsJNkH7s;tqt%CzcY zRkHHrM?N$V1er|$!{GBCK0crA6sH@9t7s^ple{6YOz~vVJvDAKhG3&_N_G(^)tFNv zOXX+=%VLc=D>`YMImYbUl+;eVB?M7tt9b+?GVd8umK z@2`}*25z}Xk?4(yTZi`xM1RqiL*6PTrxWJeH+p6E1NXFuf6J@e54-%)nw=Pxy(!v# zR8i5@P2cWW?bGJFUaGa9i((yN`9INRwHOi6#fHhM$5MvCV@H&c{V8-%JHltDwX9Q- z;0lmSa#?3Tl*Q^k{Y2o%sGYzS?D81EU zVG@sL#$uuc1u;Zzy+Q6VrQQQpwsYa7+}fck-lt=$%)A4-PlI6c2Z`0I-a+B+ot1n3 zN@?%()X%F8PK;fjLu1w&>5w;&?m~+5@vh=S*n-!Tqa)CKRAL>ZNg89} zr04554Z4zP8I&xjr(i!iN;h_&I@q9}5uG@Wt5w+>a2K9Nd+st*KO{mh6qt5or=c53 zp6eM-?+FH0z|0N$0L90pOI$N+R=FAt2PGei;fd_De)72bKvjF-td!>z>h_OUw<=Vd zA+tg-*!h^7Is6g%NGtMQ?eZ~V^D~btI~kY5-}gx zf-f^{Ri!u25K#Oe7M(yG&|CXay~(IO3Lh(zU(ivW>kK9w^)>Le2{O?FSe=5Yp8~}P z9i}gK60a@Q*sX z_GT)#ol~q^qNb1TJwCrF(ik4KZh8bjajaFNGKkEBe2a-^^!gEA zCnoE`ti%fvF6N#*xp8N5ig|XQMY_M5y6blANlF?; zgRP*&n4|EW6~kbhc0+Ul&CjZYz+1vOsa6xR_)d~AgUJ{+?G*6YJ@M<%zkX5UKvShq z8CGBM^ta_0l54>N5Ra;l_nh_Ihb%uNv0c6( z0N*rollAO)(-h*TnM=klV01(hdwcV~g$lSLCZNM1{wWt1}zyCZHXQY zcX($-kG1x{+0FOxIA|jM-JJU1U+bIBy_}j})TMNxj}$sIpdSsL>uP`J=7ToDF3TWU z9k{gnzdys7e4doPU6OfhQ9zyLD&y0aF%At^&=%u#eYhI%8~0#fBl*=>prvFJ5?o|J z6TJ9dT$X18A_@W>uFN2bLgTbsP1f~DVQ_?gl$~MXn92kGML#*%UQRaqI#l><+M@o# zfGR|L7}wUD5Zl@uJsj_QU7h|sKA0ieL)9?BJ{+#kRmK?|KJf;gIx#;W>KL*l{rR^< z3C6U47bv-wQ)>t?1qR3;#}$ijrChXhYC34ox}PVcuxtT>L_*`sh6U?0?*#0 zBGuIyV*<^<#FCw-*Ghd%#CQ!m2N{ICJU1=soo=uK5OQd0%Om3SV`e$WAz{MliQo%z zXpc$yOmE)OZ*k>P-}mVnF8xCyb|ynbqOwy(AWu%vE!(Yl{{UzW%-5@CtWs&Ay0?8b zNmLDFM1Qjz)UG_1*j2?`(ctXGUL7xTq1aWkLry%XiroBZgAPNN*~(#^)H`+ED_(uj z{^6YM;K1x_aGLSbD{^W@U3(zBIZBaARdVVsH0u2~Epsvh%xH%uvg zbZOs6I4Fz&DX{)!tGHu zr=;(M%e0kf%%>Uu!ZtYh!+xKrTzxS7u9j{4T ze8gSf;$8$ZfxRtzh^D_ZfZ=w094g}swKR-SS{dVPCs0KRzH1(-8f3E;2G3}Lcbqq4 z&CRge3lr73m>w7aP*5b8tygG)+#t!Dr7Xy)Dw$p=Pwn!tBXuOBGLcjjC1P^YW$Go9 zEw!PFl!h#+mF`zC+L7_oF$%aGHUdD0{>d-Qgzy?njg&MQd+rn@ z`9>4WTEF_Y1!k7+J<9RqJFEU(@5km*eVckv_KwH&h^ytw1DBt|51~T{!kvF!fV$-j z*-5+8uSyz&ruZ&R<+cNlz}@}N&mGA@9?EO29nz9VH}Swwnx*K^|k?5b*YNtgW?BhF=Dc6 zTddWbriZLK@hC;m5i7D?xxkz=Q)LOEp}d?2wMdgSUb(&I>9zdP`6uXz%{^ibbZWH|vr)OWEuX(T z+@9oVlOI*f#j7wFAN-Sw9}L#Va6 zTsB}=^;)w8Ze8tSZJo=`KdqOZ^c#kNSqcsw9w81c3&e;r} z#@BH%KF;N&f0)+PqSpDk=}<8!U+S=%9`i@zvX;P-@t(4<(cT`A#>_0`+wjxg6dyQs zzSZmA#ok|D_dPoI9LZ5_en#uj<{zDZAopEu7LQ){%^X;&Wq07?)9wBIrkNw2-*=JA zSvNu-qqEJ;R!f59N_j{S1b!Q1bl@Y?>^x$IX^acUDjIt&$F(wdZcY46N@GUaIenR7 zlz?{$M*s==JBi2=w9DoyHd!6X1!FcwIRL4iB^*Slyp9WhAM*NYdbL)Z;dU*YETz}t zFEk;mLW4J?22$FsSiajaD^4L;gX!l^|n&+~)qSx@Lu9 zd5wR~zdz7DsVERlDw>WeKKDl$S<^Uy<< zx7|%5pDG41ILpgug%Mbt5ylr&Ez#)9=30@oC=XIq)$1ZeBWJNPG1UkotR9QMg!gHD zmMG@6I-Bf)Gy^NTmg||_hX?Ft81q=Sqr8;;DT`AYA15s^45=larwZf6ROYd24LWEK6T~rQL z%CAa&Qjec_;XY$XF*3V*p%-9Xc7&^u;CSy=uMaE>GbjkmTQe2<_~bv#x1X~T>Jm<* z1{vNFQ(Jk~jZAE%S@G%EC-41Sn=g6~{)xt_8RAx-S4u!kHW$KZpd+@}5&LKxR1NLr z)#%G#xOrN;SftzW6IoC8UkYTwV~<^_v*<6)1k0x%LD(R?^z`7P%H=?K&t#J?G>xV# zk1zi1Q-AY{|IvGYSmgTu=03jiu(NMa3R_Z!?%cV&j@~?s@WW0n?g=Ck>aziUOW_Ly z(G+5)jx=$YXkX8wSf6(zn=4_tIaH~)-D=}`NjIOJSrDhsY~xJ3CDL-@imt`a!iuvz z_OG9AQ{Ux#Nu)^lnB_gEI-8ZP5}47bqmHM_u0(}u!ltz=J*M~~4ziczBnEnU z=@nxy{82uPk}Vua490mP2bI<^2e=XsR_eGAaSru>-#nd`WnKpF&*pDOiMZuE)}k}a z`rGM+s5|mLIDQyJVrAA}dMb+3q*oIa0QJ2y`Sq_6FT3oH>{<5@sgL)DqXmWpPu7{7 zBnM}$h-~Y4JtDW49qw4S@7x=;>9JK{7#^GkvSc7lJoE4z;|iM?K$Lk8j9F6?>B~1@s;&q7mN_8B*9_t!jCax9on31mh8+yv zv(a*CIwGN>EAC@vtt5o>^%rqA!DkaHppP8Qhzx_KPZD2LYb7H2g=Hr>aMd18K0iGP z%0tOF7)bi9a_FD>6Rc^d44K~VYB)0YcdSE!jOa^W=GB0inL8#Hg|!bAe8;k{S7cDAM@Bj6@ z@!qQ0s#r->%vrK4thsD~s_lv@1T{OGQmsQ$C9~k|+5>8(LR}ttG0d`UW1x_C&1cyz zKn_eG{X;jcLBSrFAF-qW^UB3Uq9-v~+K_(9^3p|=e?Llq9#Gb(ijpecQZ%eR1=Ca@ z;?!Wvx=CjCcD$f41rMaUVn^6$WoECp5%(MTqY~d~amnG*yLzYkwS~x97!v}ANiv#^ zVRpq)4GBr!QeeqF$7;>f*YV-+X>Sg2`SR01m{^0-vxoyt%ERZn0I-5_s&PmtcOkjY zp!I05p7N0D2TT(Tpc~Gmi#pA6%JQn)a$6@-xlxWDk@=~geK&(ftye)MzD#+t@1vXT z+y9_o;pHz2=v0vx-2p-sVzpwYta`CS^yXtg@f=BB6$yDc1YC7By@JstZEP<-e{o+6 zH@&S~orwu2#Ht15a8RFL_5b=hBcMUd=8SM0Bep2Fcp06hs6PkfU?KhY-zp*Rb!uGi zZ;k)zp-X9sIPoK|bN0AEdcgn_h|84pG_?%a;dgT| z5gm0cRMAt+JDDA(_{q^JeZIOyS*GS~%adf-9a%X0x3B)Z-ShE{_0g=xonB~uT~7B$ z!_KR2#Cj?1-&yvZyoMJ$xnBC)J6&F`e0_E64*k_B{f05m3KVr(1Krj?Fla|v0d?PU zWt`FEB*$LH%;*1Btjb*A>>{kfGJf~I1_7?JtiXzx=~+`H<|AXy7YG@Ou?d7e&slD* zi=?mRKC$O6mz#$8Hi9rCh*1`-{#$kn2MVF+Nen|_!HitgaY#@>HNB&%XiI0k<4$BU!Cjs4_G z%bs<-hpg>sx|dTm@3!S+77%yyPjSbBZHBna2fm!f8fQ}+I>wW7C2@b07p|{i*NT=1 ze4H2O&)u}pdJAAa)IeA`xkyIpMLnqK`xLybCTi9cRH@J8 zz1_SRyssf|z5mzm|LtE&-Gpkr1t2)7k5kjYg)wFc|M|7#BtORM@wP9IBv!lmUUR%7 zcEen8^nrMET2`nJE+KXA17+N7`|i$!<70&|{)3qWai9G1CH+C^@Jb;4D(zOf5^9MU zvF!iP*BDY{|0P29c8@2>=>@bZvezX^f#(G?<36##1~{>})^JK`^UNwpmu&s&Lq%sv z_MmYG!Az_?`tU3J4y4td+`N+zcQ|GuRATxpmQ$2-@v6~4S^%zSAWLZQEXvayU6ACJa^*L zJjV1s`7_hg|Hv?6p96#w2CRZ|oepNAkakD(>wh!h&&GsruP$|L{^aRZB2(6Amy2k* zqzWpdqXpf5zslam>~x9ehXdX5f^1QS?7e~^^nU#OBGjAGG*~`*+jGT!KDi+{a4WWc zB3OA`rWMU-S3gO~uHMkTK~4xtxsu-6^_k`Y8CVD!EwgJ%;P4C*$5q z@*IlnW$6qPrw{b7nY?AMjVi>W`mB z6o<~qhc(HA->AH2Tc>6}Q*4>PqYt7_JZkSieE#J|SZj#S6Y9Lj3B)Hxq`vsxl9se` z$d!Y5YkS_`5|K}o%lo}v_42?!e`m?M-?#Ix$lKwiub)2M74WRe&7z5O`~0n%JIVfU z{y6XH|1#Nk^R8YNCLMj;XKYGQ!@qZtHP@dg3G|JkgXaLv&0DdbXPL0hJy{pFKtrCywak@%wq= zFlfs3f~CGDSq3Ca39-wpWn^{%hFR0l5tGeZr(#XKI^!LdR z|LcF#Yq4Y*S<(#rp`iD5K{+jJ27fJ>n2d}8!o2=TyYi`)oN+}y3YMDX0P`%hQDshRJq_>1+`#cx%gGB?eso3yU@&ElRVn}7eWmPA!KLdu#L4Sk(z zaSBw+A5M!F0`GlNL2CVp8oEfZamTu{KLel8x_XlzTDUKpPL}Zp^CqpTrOhMl>hRg@ zng8?6l>DcgdADga%ec0os2xN|XSZDq2@TAp*F zaU!Z1PvvtageQnlx>{1+=Vmv?5eEV86Q_Ac)oj6>+rKV-|EfeHD%Az8@_De@YVH)DRcBX`qe zMYy=HIiG`FZv34HdM_f#WUd3H%XF&JRNbTnHgr4RN|AY+nTM!7g7(ssER+2 z3i)lX-7rW{!BU)Do09!sr|z`vC-<~(7IgP{dA}8Pq5<`^NcWS}tQzL&7Ai$jFo7B2 z&e5WZW%XeX>VLGS|Mos_;|=BE#fb!CRk?h2KWAbA7k3cU#cAd=fTiN4%1y;4((-OR z&3(}qz36GVU8vzM-YEOKgNn<+r60^m>S}GNQCqp=^du)qwj>WNAvoq94!ph`Mye*c zk#q#q8Rj-*Ln=F?W@BeE9bK&8AOtnV8?2HKLH<&vpR>ffBr~ik*;VePT@&=P&o0bMdgL(HbG-sCM}>`y zgtLeMPrj*lJyC$!szkJu?Ra<*O=Jj!-OtQ8&l)Bz+3Ru8BG`eM=A-V{L}n2ZsodOC zQlgKAIWLg~bUg9Z&P7fp3T>X$H|1b7o#rhgPekAMX|m`St?@3vqaY+Y zX05quH%iHJ0O<5thAjbn%<#oT`h7sjXJH)nDH!`vT1IszM@>maD``r6P9NNvNHZVo zL2Pos8o`;=Z&g41_*dy0Cq3c#A&Zd_;ec_ZFCBymLEk5-K)1+AxZLAJ>idK9N_y7$ z2n3vOp`mWt$S;Rve+DQ)QM;AFhkmVnb#ZQ9ECTBD>oI5KN%sAhds_1QB2kQd+a5ee z;oh7FU?xL2%4lYyAixF85!hG(w|M`!?8TUZa)7B4@7csbJJ1q3zO070%%SMGG+1@w z&Vrc#)xAVLefJ^7&dBUFa!TgA7=WM_W)&W2C``koiJ&e`&eV6c$y;G81YP$8ST(gx{kO-VWQRKM@t zfqZxov6Qa6K~NZd#HGQJ`R&7FpsTkRk16)17SrjtP+m)xwlj6`fL`EzMqj@B=+R9e zNlRwqYC+du1YudzN}ETbJW)nuNELAsR8Jtr5_CGBEIl^EP?Kf0h`b{HpSFdCpkAh3t)pN_Y)_=cSv->8sFnr7gTeu}j z=xw0CxY;Ror(Ty9$%gZhs1|D&gNYP>bh)t)` zir9&jzSRnD?W4Vz%-SE11XHtB2HmMY=Qp`&vcw;9|MExvbBUXXeXwrxq!MzhKT~S3 z#;!kQZEa{@%LrpD1^#@k=!8iBvQGl$4q3PRen1_KSmvfqzTG+|ay!hXoJ_}t#cBb{ zpg!z|{2@EJb}P-~W*7DGZ@EY~&WL2uNR7jWp4M<7xxO_8tFlNov1S@(B^UtbR%b-$ z{QZ8n>e)KpNN#N0p}f+ZauVvsuEi7h*yc$v^ZkH|Dh>gCr|l zzwyRlQfF5%8eL?j2k9h*#f3}X*Pu;ka|1R#LnV_cIg9#+j*VFRTiN2TF2j7@{ zKcw2IS0T^3kl|$#9%J&d(#{-9m>wNyhl4LghJr&yL85?&z(YN6(H_7?fXG0tU1hd{ z_%c|?D5ennxa?G3`Q3fkM=u-NkTmYtrYoHq>A6aqdjXNh)?B{=8kJ2&Z5wt!O2ihg z{-DrS*q-&6+p`6;s4iFj4y$QnTrq~9z~8eKU(AY7MnW)XOJH9;v83D$^yshpGR=mZfh*FS zTXEtpiXf3xL<|~2Od@&Wf_BjDJ~>G0E}2IHFzzp!E?bGDM zg0(+%5mr&}M_RkCq)K{O6uP+|?>h3xJOL4=U>vn?D`e@2mqk=KC3;atf+iKFBgd#M z^>Mag>$gkBhhlRbn`f>7F)ZyCfC+=jMP+5Cci$vDyh|I=aj>}htw^SzVDU`qL|*;y`_Z?( z^W%>K`r+;CNNlT$sA_)IB$7WY2W#OoE=r!3p~!QhGR3odOWFU#Ds z*13^!$GD}M&49$0aZTSO8A=ry$B_5x1#}aeiB-XA_gO4%nPljkvDc^YXC2H0BUjlQ zJ>kge!oz?-UB_N8^0rTCtTh+JX$P|-$!vu9s4Pig~1Y$cYN#uTT zlSKwA2AC2!OvGGO2*x@Iz~5&;4X9(J8MzFa#|1AQX++Q&oolqm$Rw|LU43-NJIgwB z3mh+QfR5$Abz!>~?gQPv+t1p(=r7NRcpghxaX|lB^^?L05m*=l#h$RU9n;D1u9nh0 zW*z40?h&=mP`55jkx}L~J9TfnYbZ4eIUQ*UemFw3l`jIZh;w(^zwym)C@307q5b#p zxKfNZn*ZUE7PjVb*c^uP3YV)Aa~~#0Eyt(yrR_D$t6?^~gDC5(9{4Q?hvm2^9YqC_ z*=@C_HTF4o|5cYoK}PFElt8L&vEt6Da5~EWltmutt-`Wu8lElb&b78FY09Z zr+Q#{2^m$Nt);O~8k{nN_K!Xi-P84FMt_jHGj|^g6txz#5o&K=o4q|BouxetP-%pr zK_Ax<6V9{xNcqI1qVLJ1tSwo|#4-UdI~?WnBMy#}vj5)S&4{rQ;kmdF-Q>cAS_?^h1Bi_LJ+3RHJ=_KU^Ef1!U!M?x0mCAmb~Xl^5y zKgw$C=iwL8ikAC~Rt^lwl;0xy@%Lx;fBYj|w-T+#Q@MS$%&i9Gp2XQ9;L@ZoV5(H_ zA_MeId$pXdlx;6wyyh$9z}YmJCo4#FSh#xwu zjjdK98Bs^|R^=5bO6v<}0~pTvQa4AzvyVpRt=~&LlHo*~_m5^pqyW($IK)(OEG!T>qN=r~mf9+xtA1`6oGB-~30%qko^1 zy|n-DhyKMNA@5N|^8#m8J?G0=ylWN?3-$@4qM^>sXXb1jWzY|;?Y=PZxp zwq{V973$+_$lH)g@^~@cP6K`WYVQb#L3`9gHj|h1%@L7c``L-L5VLVei$Fsr4U&^k zMU88cH|g8fA}a3Wz}`KYZM479x18+5#a!5AC*l#B3Wt9AKSIzB!l(k&2HHI>rZrct9$ti|8um}m^AJ}g14e9=xLclz znyl@9rNP7qEK$!ZKolBJSumNf)NfQEna?se?3^vciiYKE>y)lfy4#&MJq|P($5Fc= z;(_a7c9h(DP3-1!TXROLcY{9?aZ|J6mS$eys(HEr-b;6IPU4=;rxcmW1RxB_{)gL> z3j`mb?WH1Wp+e^FPXh`*x4kCuSs+(P17|>3#p`CHK(st}yr;N_I`K69C(R@caq3we z8#hIY;W*P^`+P^Xe;ngEpt}f&(i&eB_{|I1fVFuSxG08hvO=f4SvR*;cZ$bb~c#!Fg@KUfqi5^md=ZMsRp4F!dFE;tfQUJ7+7} z*Gu(H$kd!zgA_p-_m3D`eTn;n25UcJOkot4^BjcQ8VJ{Wj{7Wb;*)CpHbyJ$5Q1vx?_eL{h(yS`(GoYp{RQEMi&qVCN#ytA3<~gSx$Bo53XZwaZu`+3b$koseXwLge-|?Rf&`|neFYM=jG0(!| zV+LY8SskFvx0B$>V~d25Op-q`-Y1qawn3P-W4U?DP#2juCYc}?&kT!WH$5q2r=3oF zvzYP%4>B&ui10*j#(Ax9=FU!Qe8#bWw4~F19xcXfr+X`k1ilneBOkcHlVvoqMe!&B z9C8EnZ+AUvu&ISEa)h4-HgpaB{RLb=tvvH*a7G6(n1{7XCZW#2A%suai%BYrW3)0w z%Y>s>0yfI5qBz4?W*NO?mxZdj$%0K{fAqAIx+i!88|4^M%Mve%QEc!44HL+>2U?sDsZFPfW@on|HLE1reHRaA_* z1u&ROd}0@GkjHt>QkG=>0q$Hy?gQ4!HY@eG+|YYz5H6GXsadBwI$phNL62PPN>I@3 zCXgd!Cngpyj_9mU(--p28NKrXoWmnDMq|lQ1prBiRD$*sY!Tbr)1=66e2N`L;=@t9 z!KSyJ9T{i85~`Ms8XmFkhIBr>BkS@#h(78v(PN0+0%CAjwU;q&|y#o=H@0CG&wzp0F1uuO`K2dr?62vc05N37TreB)57)hAasS* z1KofVgvUU~`rO49l!~lRJp!**(DH!TVinisd}utp=>qgkpMX$am5lVN;yogxSBIPC z9$X&4aU?;c7eQNtv*;iyJCcUnJ=C#I;C*1aoqXe|8v6iX~*$p;~3QhyDu}Z z*kI*^o1Hx9LM-5%MWS6+v5TurmvOibLJHR0uBExEW)PU{C!#0c-YG-)M9#NgZfSj0 zQjuW`fe%PgWSa{sBPm-PSodYRDi z+>Cq`E0G>WFY_$s$sqHQVvoK8aZ=Cgnu-D{C`W|y-i%v~ZMg70kkj?thM5tX$Xqbk zOgTo$mG$F4)7!S!D%hBEl!u*y27GM+sVJ;4G=VPd>>1#S05(e#&p-QLb}d*OrxO0EKKM7vCN&$!9vNr<_$Kn$ z9MR-UPd%1q^@vT3ahqEs8Qu=spoId^U+3omLuMA^DZ+VHoawphb812sP%=Ga#vfP; z+@ItfD!k!d!m-UxYeZ?#pm>7=t|e#yNNR0csGkG=(?>KF4SXPUCj4Uaw|5rf@E}!@ zJ~)Kr@MTi6q^$0Fz4~;Ab0F|IGIjEi5=o5Q~sg;6)&I&cIK#55($U&*?7nyiYD60gH?VZD;}-+Xzm>sdZm0 zMb^vxHIubI2G+L9r!Z8Y$!~Cykh2sj9|nM^#?mYUA8i~rv5=EE-x>Eacr;ptV%30; zfgjdJpVcU z_*RUb)xOyXf@F6U0ANTw8RX4tk)a9(i_`2+ME#ZOuyJ@Wqd!FxdSbx6VGrIS6;Wa@r^+v< z7N&x>{kljP(Vf}{`>dXKOo97rx^iPNnZ96{gNlcDVPX((4zqlb1<016bnw&qQx!x& zHRPm=c-LG++t58A+z`ToHiwVNgLgFk+~m|;jdSTUn4&2RedXL@9+C^!py-$+vmk=m z5k;FCwYaMsTcKdnkP~ehAKq|F&MC~)81G3hqG)gjQxG2(5xd$eG|mIei8ZK+-FSR% z(-_mR)oe_aq4|%2H5}b|=L<5kb-PSz$*N2Xl9n=hwEs(uV6eU4o4At^ex(t04m*k6 z-)yzM64s?!cv5?Uc-XxE$x_t+gW5Z_nF~a#*OBZ1L5V0GWZe4ZFSWH@M_;C>*(qDJ ztg&}uZA$+%htdO=@w%o>JDU(HMWSUiuLIIh4pNXT{#!F48j?b*Q)yS05D8gM?XK#B za`>`SWxG><;;ShXuZy7VGcK?QXbF7;x36$BDUVVp1HdCj$v_}6G7KO~fFG(*+0R4F z2MH#3i8!6fkDX8) zyg-GTgsxP1Kou>IaAzsPF@NS)!l^EEt5yZn@htQREfEBrQh@#rhnp3BxGJDahCN z!8Z9bSca_(F{UC}Y?R0dNP^3Q1lSDkA^P$6?@oenkD`}&Mn^COKLZVh4~J*4*S^qtSwHGdLBIZ4WP1U2_Q%lNU?n84(l{+D!zJS;l|VedU4}nXdSN z!=~QosH7q-qTLoGLZq2t>u*v74^f4zxif`}Cf!lEo_?;ifU2Q`#XBo{Cs^9s)#aQ` zD5_GUOx}(#SHHEpVMUyWcqV_@KjQBzjYxl`sW9oZ3va{u1A;G3O30w3){X@@*nDx# z&XtCmDdQLxsA^gE({T$kY++$2f4q>aG@{4{Imn}lx0H8Tu?*CG_d)Pmp*Fn!daCjd z|ME`dpFF9U|MklZyXMb$-@I9$o_eC6yY<1o;;XGOOT}1J=shhRu1VUul7BOHvy|@m zy7UXanUQta7>0a2@FK%g#q9Z~s$$ZP@RI_DTHA{z1DiD5`?ZGVeL)kl%DnPj1d zG63U~UEJiJNiyt&lb{kN;_4;skoJnXj*dAL@T5Dst=Te?DZT*k%U4a|?vgCIVbYrO zsc3;J)}cC@|Lulo^Fla!7_=pJ-IGO&(dX{3cX*NdnTyK-zH2t#bIyw(fg+mH?bok< zn21uRzV(jL=73q(n?tb~Fnw9$^NVE zBJbM1#7%e=UW}~Q>pGU&$W>25*cog#01i9r=0x^}67*Bt)b z-4q@d>@YS%=M#sT@ECN49kZ<3NADiq++M$c^GX86djFS`ywRwGD?8%j^h{bu_AZK{ z45*ZvP>?3c@AVQiahx5O;=u$HO@K=d{$Op|WlTOB&B-I$d`pE~oN4=xz}#O(`)vpV z_HvLwUtgE}{lpk>G&Da>Vr$t+Qq0|Ob286N*tt)w6S!{!@W zUdBGz|55{v^7(QxoM+KPCz4vpWftSh9a%X}p-=8{E~Uc{y6+3jlp9>T1QxsxOc(eG4MW8jnSK$~8C3{Xer*!bvdX&w>U|&~rXUFdztlAgI*u^nFqP9E% zmBGpJCcf39jfsBRQtwmk2jz|Gi%;>Nx4Siv{Y6*KwukWv7*V=&*)32qhNMgohntW zHz8~pSt9s@P3xb3zWPSb*oO8wc2G+0!&xWGy{kp8wmECLHi}Nj*G=76aE8DdI!!*S zVKYY8ze#`{QKW^0(J&Kn?V+oysZv(;CvA%keTEh>@o_m|3(yCtgv<;4KmD!=TS~__Da= zF3rR`B>V$yV*V4(4NHPCdtg!q?<{;0^`p0Y-Cj29sTiJUYR)*v_jkd>*#fQuJLmN%I!!;{Ea zMlQ(Bwi0g|R+`f(!hC(M0ot>I#(2b{4nmcoO7PLlzJS>Cm(j$G5V12yM@k=+^aQ ze6)~Sz!YXQGPYa8jJG~tH^7l^ARF{W;-Ow5d;xgtUcHD44Z@{!TN`DVFAZ(Yo1A2G+@I{Sstj=?J7X&F?U${T=m8^b z5xcszHSs@B%ywHhDh~2DE?!toQ|E|v%YV8`Ro+j~&)9QfMi*p8*H3#i#q|fqNaimh0y7j+m_cK3J~ScCUZ(wn1lo*;XYZ zI%4%#(_|c^rIm6yUSQ@v(*@DKb?RD6zxak7q`KrQ)GqMTZZZQk^XI3b)1NtVnBSFcmYBbD5vzW{?kvoE1^e@uDxY-p(FMpbGanWlSlt?_bMZ55Rtwu{8H3$_Iee@uvULz z6MZ{8-rfw?2e3pCGbOuL{XyL?g#e}D)Vn;eFeW5E1+@k>-9SlUMi9FTa&>^KDhYhqcS%Z-$-|J^iurI( zXEJhM^V?j+2(-n?=GR2-b9cala#)xIin)Zxok5Z`l1p;3dua2p-yFzPwp>9bphA~? z%uqurOI)=puV7H-xKW($F(iq*si^3Avl)oQgK!$6+L5{WdGUL_`ijC&>uxOg|6Sq- z$esMA-PO0!G4ZFLf6mJKmCIN~ONhajqs%*g|QXwd4hfxgUwl5!jrF;N)3|jmzD|!C(TPk z_AhS^S)}^ZGmqa?=Qkxs5$EMY0}>%oKH#P5)$x}S>o^%bW*91s6@(*b3zg}uChwk= z;Uy{M=8bwjp{fs+E6gC7zjq?ud~RiIWoGuy5nAz06bmpV2uRa-Uqr zhsuP#DeHnjsZ`Eek_=<)%5&H_vq{gQe?LFNh9iog8Mtk62B*8^W6tn2K8r!pEJnrD zuv#5XS^}>hpbuk&Yt3y zl@P&rXH_UM1>^TkAUfIs(Dl}EysNRi%|1V+vWjWynSS!FZ6lieeq;XUg5m?oC9Y&h z0+rhF8zao;&AwxKt;@l8p-#oP^}2lyQ8QSsAaS<_Jc!B2nQl<|N*$#%_4TBTfK~cJ zIkfP3DT4H=oA*uDT%*L-f9a}^3P_>O%2nFAjY+6MYVIaS^~3HZG|x$Yxpvu>z=JG{ ze_MJI(GLgsqEk^|rYufCWE5udGY+&}OwW&_=u?{q%IKKBuA!!PTDS`;)ZTD*Vf+9n z_EIP``Z7S>m&`hIk$Zgk8UKrJt5{{f*E|yMjTXUciaFh$;_!F{Q;?_4lqO^f0+nGm z9d59ilC$-$S*PH{Pq2xm^n#>>Z~!E-g*RTodxp3SWIsUJ0}TIB7>w7yA7GnWhin*x z+K$}~qk$zB*4IR&-c_9&MlepwV)6n4?@067O8iuA!Xb@0sDI4;VUN^9x}>2;@ev24 z5Wuw_=|lae+KC9XRZ9vo9yw-jV^O-)eNV3)J)T@ZfhIiZjDPiYsnxPrm_evyO(ZTu zMB|h|1Qu1Vv;`VCBtT5as%W0M(sivxtWRB*iX!t*I3Fj}hF)q7C3KQ8(c}FAyO#e< z<>S^;>{^s{2SMc75ZppDX2-%^?Q$io5V8`mlO|39m#erg}1ZMGlBkfts@#O~384~gWSzfk><_&&|?Rb5%BYc56 zd9XJwkZ0x0!2B`3z!Vni1SvQ{&5BoowQR*Zianz*wSN|(#?;q6Wo!|S;o!*(X6B1iCI06*3NI<|q(Ien#wTp3&EhNH^ zae%ckMhlA`Ni~*q*eh*Q%VF{K6w3nZ2f_j*)VjyUkYsJk7T8d=7XvKnn|993J^$U_e=?KF*bzedKHu;6^M1cxmoV$ku~RrY#o+NY zYemTUZJdHLvM=`XoMuWSrx~P$zRvG?USqvmgs<|$`*7Idi{D=uION(UQSN2(Pw^aRC91=~k$>=d7uckE{BeB2~Ru0tb{EMA{7EStf>%(ra-$WM4# zEP&PwkoVwUWo?Vjcp(>ln~*nCX)?>Zkr)2ZHnxitR5_C ze&rSL3Yhky$xp7xl4_(M_W)W@-67)9+m*1UY=<6n=Mabqv_jbo_I`s#EG7f5j{f+k z|LtFDhrj=B+TzY5(a$2aHop48|AuzRhB<;xQogy(njp-EoAWmv`S>1URLnWfwDwvu zy+u3gLplf75N0vXIj1CqPEQx9Q$~ZG$HzgyRip4(@(4xfvrql=cbF~XYgdaWIfO7w z+nULvg9ZiE@w_f(T(oG!?Re*mtn0vn%#e4Lzq283S=4OaSa!q@t3->yLV2T(h{E03 z=Fs(5l%ZWOK3HQ_IYoyM4f1oEBv*S@KlT6GXvj_#W^b7++nw2mCFR_eG!dMU>=sPc z&A^=$OGhKRNPtIX8=r1xO=91%Rm+zms`-GPg9gp&4SF>()T~xGmU)4yuhL?g7bH8E zs{Ath-8M)q&D(W3DH4NWwAT=1GCOw{DXc7!O1BJ01J81C;PoO&13ZHPiKOq18GG|+ zRTc(yQhl1M^JS%Kd1|{8>QUT>fhqh{o>he3U4JweDuYGV=1;)NdRMk2*bPW@x_h_KfaE4^I!zzq2H`#Wh<%0G z{1L|0Tc@L%hqZf?c%B=~K3?YxE)Ur$Ad0nC`z=%@@w2muFbjxtI>W6deK!otGH^NG zYBoDz)wEj7+gYk)mVf*7avH0}BWvl)uhZbNI@N?NLCLfXw>41yCQI$b&k&j&u>nM^ zSLSr-YWAXPrg3sVM7cF2`64LOrg^&u@2IE;CD~gVPt-3! z(z`V$wa(je6bH8LBz)a6qTm=^$ptMRTr)yba%KFkvV*ev{$Y={;b%kBBB3<=UGvk? zH$dq)Z7o>mt=HBLNU{LHK;Fp%`p-lOZdF~yw%pwran`{|Q(p;oUV&)<1#D$ITZL$X zbCZQ!s5TO#J6gZQ?}yDYa&&4tgSF)3UUBBiA@~a26%RDx?>t&ZdF(Zq&|&4OQXxzh z-`XDZC7L`aqtg5aPhoDtM)0`dwb+?h*50DET1<^r@Xn5?aVkZk4h5JAZ*xO>*X77( zM!M=3fARP4r!zHmmVq%?&~~FG=fagXL#%K;rd>3mdln<0-uV2=iz{P0Z^y@YU9@ zoHZ*a@dySo*Yb8`go`Rs}!ockbM}A3YZo+wVtXrje+oaVDzLpkJr#O*v8P zvrFHVc*+90Nr+q~{QwufLUE`aY#-1|VFf0yfQT6W8mQI}oq2BNANhR{#)gbfD*EU`G^*@4Ch}T~%&8#$;%Yzn zTuHQs-y0o1G_lEfqAZcm_G+gL<`IAFocK_;S;qI{?9F5$@;~aA;s$>BvuJ3)|ew>o?jf>2qM|@2)Ab73Es5pY7hh28Q~otFUEK7F`ExR zOp@#FByfu`$yvg-Px`=!xmUWJC+mFv^8GgsNp{6Ie4Jt6uf`9w|R>M~{c_DsDc>z9}P}##h zkNE*&ja|8xF`g8n05|o-IrrweWOK3JJy(*K)d@Z0>Kxjndc|qY;m)ncWE!!a4rXVf z>1wMVLM#j}HHPf+h6M{+IsyDj@dq>%YG(0qUH|!bZQp|d*F(g$DZa0c9;J@gDMh1u zKbYKzZG1L6Hqq~c3ivxV^#|%%tt7Zw+@X!7N|XJ z{!V9+Fm_1?{Dayc&Jhj9&_2v)sXm5VJ)Ol-umel;HlmmC8wZ?tn5}D;u`J@A(;lcz zd0oAZcYWht;kvME-aIZ3HvhLC$U9Jw>O0a}xlD3#jhYmoYE z7fOR(Sl;+62vlV*&0@nldw?li-`<_-Gn?P7yft%kbvY zL}Q|ghtEn?7V_vOd+vHKPzBAS5kFc{vPQ!)bQIVu{rlj&dR!LgF9#DqRpL5`sbfW* zHfZva&Q60O)wHZJBVg#0zdDm+ms)v-D~Mh=Y(rkrpo~Ac`bfB5@Cc)1pX3!W#mS9S zW`lOX@~30jX?}HgdV7z)guyUbD)xZC&zHYq5kmZ$ZW^{g{pfJrCCI&0w)Ozw%H0)v z1-E2Tcz)PTr^~3NL;5(|E}dcf{DPuIyjudpGB%v*{^&hZuIPQdTWp`434As*hkNK8 z``o2gt>b>H&)D>x{QqjML$K?Of8&vWNHK-8r}F?c?#|LLYx*KAOl=}mxis3Vq4{nu zDstx1_2T@xg`dMHWHA|Ti^dx&3o4p#mObJrqnZXj6a3U6Mf>a~2jfc&%bAJQlhwz} z2vDt;phoUVw_8&1FcP-Zh;)=&Lt}5bG9Qt`}4k$J+AhAy2%{LwBm`AcMz|tjD>wH@Zv5la*>sZ>^^f&?-Md0HQg*7JA9D82Yat;wEPGo}0F#y63Fyi2RP zt#|Dcjdcpu;RpV3M+5q^MQA3A>B>n{G`6*Uwt5l4r@M_gn3m(GX3ZGD6}g;RG{+~L znEjf9^D9{B>|LWn^AX;p(?aN!2*P2#h;6ZVjY$B?GTQ1v5k8WbxhW~5vy-w$0wzh! z*@M+Dusa5{T4Yg^Q#}}*Zu9JxL3Nr#FmqSnFvdkk>q)S(HN@Y+r9@+qoJ^5N$87>R zoXlp0d-~Hi7Yg3KySTr+`wyG%8PX-D&%~R>*9A(fv0w&4 ziZX}{1dkFxd)Hv~X_^-VdL6~cQ~dl5e$Pu`Pq$y_KQK9Fv)H@B87hx?x;`(w@u#D2Y+>Kn1c>uE)}9F-B%nudk=nr4Cd zD8keF?~lyx=_IA?dds#+X854;LiYtjcuNo%w{ecg*v-|vhLmk6^_|Yr<%552{*BaT zmT$^L*xIZ$i_Rf`#X<#hC-RJyx@z|xFG#_s7G}VKSX-?{ao#Ol11a98q6P!ue>O^oz-9W{%Qc~-D4G5ww zv@L{WMW{Ba;#mOK1fujf1r+t2cKYOPC)WR2NlkY~&5k z5l0s!F9bc*G78UNNr(n(bExwo(n8elUKmNMM@5OI@8GuP+%jy9*-SE#>U%{g)I$Os z6o_DdrHdYD44q8e<5b1l}6o2|xEqVc&VYZvc$Xyh!`L4NKP6MPqwsXhGT zq(#=OQI2QOgr&#WPAt14CKKtriHF+G*KQI%KV7iq?)I0c!g?M4Y+31NROdOg zh(^}rls_vNhfBaQ5wzb*#|U@>Cs8YSm;PA$plGlgFrikU@|67bSZTufJbaRD9Y^4n z3EmX|Nr91IO#}N$iPUxRe|cGmkel#^rAM~*i0b$sJRpE?w$esT`S?WUm2Uw014dn% z*3v+4nM(pfSC8me&}ol$(cp>qy0-~N8ODt~bfuB@w7qt!oxaJri z^=2$9iI9fc_HY2PW!*JY86 z@{Xie)R82pGmNt>;_uCae68y$(&}AM5=3wB?XFrd^ssuqLOxXWP%GjTbdn;e~w zv!_!TR{t85i zS~1CWORe(p=4xZfRPQG{6Tnbq{4(QT2EQ75adGuWntyq9p(Af!;sDMxLu?8B9^VrU zmJDjGtDJ0CPJ3p)L+ai9QHOA=q>LKGC4wfQ96en?XT&$m)9B=mu~yF=M|fK<{_bbm z4S$u=eB=vYw~~KGm3O(Q*^KODzL#A<&cZFGa_%pW{qC=bfGi#&03Y`WJ5?s1>gtQ1 zQ1WmfI8EF6^4jE@IdbaY){t(Jt^YLmX?nE68yRbieD=+I{@!=n8>vZzSC~Dk0^0k{ zo(XBlbf##w3y-bLegE<1F73X#%KvzCwKMaj|MSg#x8pzFTs&=*NxQ*Stm=<#_S5x= zVJf&Y!e2KvW*S_ERmpjC{I7ue!VU@~^!HsG;GzOH9G+JxcD$B1l zLg2e%JckIt_>l^|@+=e=cVv8tQ*;=&XX8jUe$iialzDmY9iWP)f`6R~3ba<;k$1Py zF@^-QUY8Ekgud|&eaWdUI;ZNbb{$OF#@){uPDSQ|R`?Z))@6il#n{yK1gX1xvt;Q# z2t4-aH3pYZwa<9(W)oGRgjR6$T8!^LE$<#>r`G?-Cy|uM8(hU@*wNW?NU51coxp&` zXlDlPDHBo~V{H&62|KTis*Pq9AQ0F1O#9Uw^H1$G_cPhk@q%UTJt>gflmS`EyQLum)?t*;?IjVL;e+8_ z%;~F*7N7Csw&@JY@s2^EVjCMFk9{@HZ@->KM6Z4?+qfxl^-SFBADWZ5$CVx*Z!1XD zqf59o%Gt|T^|MYj{L0~7uF`z`@2~S+<+ALE#>(dVuZ0R))A$H7CjptUm(A?H%7v=Z z^|Z{0Tv4k5mR66X1v~1>iQ%wt6F;}rn%-|vZ>B?!4%BEJY$RF8VlB6 z={0xhhr?O6jaU3wMVU|m}S<@naf&`YC zzf;L50aQVpA1t{x4{0@ie zbM$1}1@S!xCrcWAQ?&*)&qy?hkxl5KW_-G|R1l3%D5P5$<lR~3rS;202(QJ zE`n60d7#k$1guBQ*D!LAsQR77IRs>qiKgI~&M_C&ANlds*>T=<)iejHYgnoK4Iflz zUN20H-im}9tj$&@_Hc<)RVCwo#qmpLp3ELl>C@UFH{KMkj*hLOOHwdS%@{uZohYU} z=BePGh=-l;TpbhXzWsg~e^iaqTJoRBGJPx*Rt3j=Q`pcv^7B!9I&!I$3u~d)WAWb6 zZ2VPG*stPU_3gi ze<=nzH^JaE$%;y@v`KiLnX)|sUdNH{eV@;d2_^i51&&`Gqans+_ zX{Rz7rAK@7GF6pO=8!x;|29q6>&4Z>ZT>^aR7dMUHrWDf(vY4;%G^drL5MiupxrJVRjaqWBiLaMz^As9%mnM9#ED%c2L#3 z`Ycm?tB`AlUwX^ryK#Ar$cmmfJx)u0mZqmq0xbh;sSFeQ8IkdY!J7TFFz4IR2j8AVP8yeSOs#F z@zdN?-9GsOUR?Q&((8njXzpt1Yy0fYfB_~KeP}=dt^R?3h6EpaNt#{OkaH-Vg@&qi_6!HZo*>+j z(^o#!#cW+pPD)wjXrv*W@5eC-ug^0Q>GUyS4)!c-OTm$yE|;wkx>!FX)hw)rU3uIT z(2roI9{s_}&F$SZ|r0j^3-X<&fJ=tw^ z=~$zf@tmij zgnb@&C*H_WztiT+i7H;7WSk?7UBCJ<{iA)n)~94(PfAONWi1lt_HGL&0&Zj`r8lRP zyZ+B$C5#ywPn&i7v~@n#-j8@|pf@%qyUO)pm*RPhk9u6I7lV8c`yPML(UUq$t{q>s z4b3mp8M%*_t}$l@D_U;=O#>C|j-zQILmwL-n;)^1gpVc?4x4}|BY64r8vWp%@-n-y z%%#?whn_Lve&AqIkD1Bc{HV+Jh0N8P>F$DGVNZ)Eg+t7<(5E2*6Pnx8rn@7EZOq@c zT<^UVDbt%@;}3qp0sEhGLYfXE@46f*BGj|?B)?O5&Kg`@u>j*7^CiyyDpD`j(3OVO z0^T~z$V$rMTokt8=k2-lwlWAvjk&a}n`V_0H{uA(yW@35>%HC1#NOTpQGTR&B7K60O~yfkeuu2fxJ`CmV~!>WJY7<({IaVyVS@)dkcD`uTXaEi zm8zO%l_zS`e|5b#R@kMBgH1C$hmd`06rXOkcD&JDzgGyP1R)u3U>Z!OYMNHX~sBhdF+o}5N7+$c)WG-Pdz1R zLVCHzHP(IOPLaw}GZT3e{qPDt$?pM{_YhM@_!TVwlXhG>7DDvKKk8RmbLbe`rcum% z{5=*KvlU7`+bkk47V-vJ=wi2*XAwQD8eZ96QO-VTwxH-UfeRcy3<2t1OeG0{ds8^Pp^1t317#dd;!KupzTWcPTTm)oU2v2?k=) zzL?Z8-%uSA_!41R>h4**7=|78iXFpaMX*huBz*~K2MJs$xi%$yOwjzlpzf-vW&DYvAC55xP2Nz_n zvT*&vZSS`~`5vLSES}3RbtBI|FO@F#7C$?bss^*n3p8>Z0sj-bx*XcT`n)pa^BvD}_V82uR#C#P4oaS0 zI4@^5WBi@a$%S}*BA=_Dyqt2DA%D^lhAVRnkTC9y5 zxjmxhgL-YkloFOTc1RNWQ}bF6S?-=!oN(thQM2EMG&>Bb#Fw2?fXv9GIuY!@puZnyAY#1-{9gqKY)@1gFM}Ka16f^SX(XV{ildW z^jwM^AKX5{(pMq^MM6e?DF`9h{Y<0|q7BRBqxkr<@d=y?f~+Qlk`lvX>?@ZBYTaY1 znW8P4*g1TJ;}UzMHwCzQTcBBy7lMFJ(SS@Ar^d>D{<(5FNL<*^Bxh4fEV9J?RPX_= zlhdR4uMyfgry+%D;V}oyn+BJR0Y{O@8@P4QzS)rtPx0>C7yvQfj3A{)zwwnY#!AnhWc2Wv){H!gt1^)Fy|EKak(1 z!y8yDr@bk)J5q()-hI$7Tk%ocmsd>Jk4C&Nf>PiPj~M=5_rh-Te*4o{mN#nFKAnyb z1A10Rb0MaN4%pg-xwFZ9%RmCx{_FLeSWRZQGyuo2@>k z)1Ba_gyHs%0|i*Lw^4Q{l`}52I2I~IxP6m=a*^?z35`afXpCy#|gBM+R zc=XtR`^$MOd`KETGtyk&IKM2#r`Q#TEu?X-U))jF0m5%ij?xgpC*Q%|J7NKSRAzBI^^OmqbRsj5rQKV zL$ZYJK#KC@!_AJy;R9cDUs&q+0>No-q}?#!2kb5uc+#}US$gr4rTM@3`@~~iWJ^|A zkW-zo&v_duQZ+LoCog5_EA%75a7!uU+$#coust$XO)vnmu1ef64{h!50)HOg&%6W$jF(S$DJZoH z7-yM2tz|HZiPruUv9l!`P#v6#b$V05qvKQ%EM(iZ{ixiU*!l9m{ZZcB=s(rU3yr_O zs9@ed+Fbs4?k)E7c-hsX40i_9?{NYXBzYouF){=Y*-i^wz1iihvCVBH%8%+QmwF=v zvIZwT(QWOkCM(mrz_iAC);QTIX*JJ=SQ?!X@ouh68Jih)dA7vPIH^m6n=d9g2t(Pc z7|N6i-aWAUnbR_lB;X460`YZYfOA!yM+XYrf~>6{Y|xdjy`5dzl-&%Uj_QnO>}6=f z2~z1V-eom9WM5q$I3ODQ^1pA?^$ZgbAE?W{cZg2kFYI?C2Ow~*wI;lo2E~o|#N0%Y zeC;~0Zt7QK#yM`yfx04_-6gY}2d|usQ680+?LPU7H}oU?PgJ*-s~Ft!74LwgGAdM* z?^K4?w~>`aXO%&cDHVX{^MAfYoV%%doR|`y1}iTDSBF92kS&!}07ytHt)fp>lT6{o zt!;RRt+3}kkSQq#q=A#!wfj2`BN-oj@2IDf{xNnvWGq(9lnZ^!+DT}ju5Kgy6 z&ZSY-Mn^(gD50x{1J1(w=;D5ULF<6YyUA(KzYf)VM{#4BfnJB?p|TbFq&mIB@zhG_ z6%jAzeqkLiw`+PF?!RGhKC#S{icbAL~>-4pCANho8{Us%$* zFf7H`WmR``jtv>J2@aaWauO#?^BY@L#_6c=kQ+nY&J@ra1S*n?m=VVI%!vHkgu5_~ zrg5)U>|s-{9^o2e6^l4dn&5)Ly|4`?oQG`*g~7$33$O_v^Bo3Zsm{85bLUH zK;GDfp9TF0hStqPbU%!jmQ|EpR%gJqtE}vz;uWNYlqN^?bWx*w2paxLFfS#N4LSrr z#Bulhak-IRm!N~e>=Yl>jr2x#gvc81CNE;4iyGvP_4(%B3TFK7UX;RCzYTS&a2x5D zq1(aK!J&}EtYI`Yat4&Tu|UI<4YTM6bgPjFpFVW% zAs@C#DQ3K$h`J93fVYnOodr@l_KShZA<8NQbu**f-nq3K?Qd}C9+uY;J-dqY5cQ+x zS6Cf83r6tga6qNZy)_FeYmmSuas)qV$pNa|%zBx1%48`FHGOu<#q=akGEh{3P}H~5 zJK4MJuXUkPzyv8{sy%o#L1D%K>K_L2*eV@Ns9%vJw;u5fHoT0fab?QZ<#F5s!5o`Sj$cVIy-wNPNgsT&tZ?&5?Oh(=JqsPY(6 zPl&roQX?6&MYfU5Temd{6F8Q~b5XiR(B8VZOUr3`#m>%BS%;Wgr~SySoU#j~Nuu*f z-GcvhWy7g1|4_tPa8+&nb&yw~J!XCD7n!PPgI-g6e-wcg1jp)J>IJ{dT}&0$Br9hj zE&t>_!hMfZ9`Xy_ogXuy))IiM6Y8Au^#oxPx6QMAc*)ML-#QD7MhoYmw#idNCQu@+ ziN;u|LN|FpIi`hD0v4S^UMTN$v8py2FX^-(4 z2x=^Q2|4%zk4=p|h(2=cN2gv)3U!XVY>!r#im}!zU+GnN*LeZlTOGStak^p1gpa1RsJ7^)zXYwb0J@qI0l8ZU77Pdh$U zE3@R&zVUbEL0OzFS?zfE45oSTWd!aNRd^h!2Gv0BLnQP^dh z%q-VRfPzQCuDx>pEp-OLta7H=l-yh{1mz!I)kQjZuO>H{V33W8+r z-!)C2JYFB|jh`lU@yf1gnx@EaviPxpM5Md9m(Q5?^mMkNJ1pnlN4k#RIcdS+HV&O> z|KL+=<6gc#?T7arO|(GuLTM$}#mmUgJzYNJS8Mwe*O|Xvhi2^Ja$4p}P^VF7#AcV; z5m9*(YhpmxWtlM4nN#QZEYIWr{qHz2)A67PKmHD zDp$LZ85~BsJa$XP3RGH;9jxiz$Pk4PsyWhX#);I&Akvr&zO-VBFv?#xarox#4Zw3M zkAegglv^8}WR zYx2(3dzO2(bR|n3qms29&WV2H`3}y$H?^Ku5xByfe*78xwXnpaF|V$(Rj>1}j)kh2 z@zo|~{At6oN%(N)FRwb7)A=Pry3)tk`b8ZR4L+Vv;yC|K!zw@dz~01cUfF$-7J{k$ zOm&<>5@6ZFx?mM^nheY7s_|KZS{R+WA)^}d4hr)I)n_$q;#paRC4@9elTSFFKfco6@1n1K`Oo)qy@m#dvJ5SN4OtXJF6~pyhHE#| z`RgSSUChp1;oLf#2mGTl2^aF1a6UNl(3IepiiB7#U-tbu3D|<^wXF@uoSOJFjG3Gt zGcMCuaY()B{2yB~<&0f$?&X=xXvI~&ne(u%WL`V373oS`nLUSENbaYccgsPY<80mY zg}f)Lr;AXiTCGI**~z-{;3Jh2uXlu1zI?xFF_c$2UlLH);kIYQB`OGETCgNHWkIRH zXb&!hQD9;bk0FGWBl6pJ1Gz{vU8BoPXsRNxC)(e;JS4L~YvcfJTIEbsP8X#DLn>lr zH};m|yK#8H9j{NGa5m-H+HRN&6w4SzHhT8Aphqj#ygFs=>cH8o&9z2;JUhbK;wT}J zkgOb-c(JloV0dA<>uTAWH%({a_dhR?dPdgh{b7a`?l^{Z$UV`&7o=T=f2#Tz>2=o9#C0ra1>OLq9^3*u zERwBVWMnOkNO)Nyoi^NrI906D&-s98U6|T`!@Jr+u^tvBy_|^~t47B(1I+0g_>8rZ zDi8i?6dkftsIP2bLWS}GJ5HJ7((YpoqR}Cf^KN1O#RgAvLv3(`P~?=iYGM?HLIXhX zf&OTP6w1N7vDXMf!JlqZP_zoORdd0*j5SR>{-&TbtJo$lV6gZD%)9d9zzU9(in4&Z z&q3Au}$Z&Zfe3)unfhU;P<;!fU}Fg5uLuCrV_*e>^49-T!E8>S>o%+hlcYZ+!I4 zAGPGb(=j}On1Fdiij%36mkd^^%G{A&3tBm!$fBJ53tzCeoYVy8DzQ#iB9Lx@Ser)S zdQ9=kNrVJSXBUY}Ouv&Cz-d5V>b{FN3e|*)aFeyBp(1c#5|5B+LXSHVVCC@i0Ay0- z+{u)qHJp^nox5%gnstQ*@G|n(03&qPmt(@9yqPHvjmG_CdLGsk~ zSoNdX&GXuUV;c=CLq#)@R#y#O$tMEoq7J@4_zSJmHq`lh4bqva^L$mck>f_M)LAzh z{p=qfmNZ7?)lgMbN<)&I9&TgF|8c{_g{519u8zR@9DV+-jmn!=ck2oX0#WQtOF zR4pX7)9X`ddb5C7Hi<%5*_*(;hy>7U+en{b6DSnig;%w>vplq&n_JO zijmj>cjTm=mX|&F-P^YcqH3)iwJCYd(y&uqp|2^Cypb}ka#}#o5O%g`6-g<^wvCky z*iy2R>=9|lLHxL09er0AAICLGYml3CPAsZ5hM1}97j4G2AAH^S!}q_`UajhIK94*l zc`!2XFAuTq5`=X4p{%s0)p-BTrcsDcxsCYKUpD{me+t@F_|-c7+dbd^aQgo~QGY>k zLVqN2Q?&VYklB;jRlz!ugT^tOAjsL^3e|G{zvlL97UF|P=7utr4tyq#J?J{oa5w zRE`H&Kc_wk{izw($<2LR&U`L^lphM1n@Lc5T(XE93ma2-U{Vw+;58ur8D5$;^N<}! z{iJx(G|Q6)k0bXyWT~}FJTY|E8brdFa@MdqGWJF;<}GdsuehfBUu_JFRY zT6Y9dkyQo1xvHYs`pGDoS9x*jGIupx%eR+BmkU`i0Jj1zFG7Apu{U|j+NdFwjunm7 z#ITP)4b3lelp+)g_F%Go93Qmfi@jHJS-wHeaRtl1AuG~bPWw#)J`xL#XQXNsl3`PV zn83%ouA`uJ2K@Yf(V!U4>w!H7-Cc>qy^@JqLe+vcQQ@Q*-lz4y2A}x`4N6mfE1sVd z;#&Cbl!I^lYTyngnR1%z<|j^hYE4dFNemhF0ZY+Dt)@J4g?-$!*V|=A!Gq)y348o( zXun_F(3hDKN<;H`5>cJ%b(=zNA~gHL9DUV~XAFD0$`Rscvb=P?#A$OD=Hu6CeqYc; z?;m(PdTHQ$`+j&ItjAVG<|H!e^tXTGY|im)I>*xX!Ds|qfZwH$?uSIbj(gZ9`QEpJaY$Xec)@BzPvT z6?=Gr{cYkrvNa^*kA4q5i?8<|@4J~fdHF!%&~RCK>t6i|{Xjsw5zdzHhHm?am z@G!=e=Ci=V82;sRwqBbB(`ErlD)y9j`bS+8sQ{a*g3K+>FUut8_UxIZZ~rN=i;&mhCs67;n?r`B%TL9|33_2mr`dX3YPS#UEbRZtD7StmMlWv(o|G&kS!4(0JN zN>YCx*He#F=^#;y_j!FP4DQ_mrk#zS76(55P8KU+b9N^1KoyM>7fiAUi=Sf{Fukl8 z=QH&fYYSA)x!>esU$dv%+WRg_e$}c}kPv%PcQ`1!@i5_Z7zP}3T^-EM*FR0uUs|n< zvx=CyqQ#r4g9Sa2+iX6MG5L(^Muz5MBsUKcKzm=JrZ>We`GkD{I0V_i$C*{Ht!<4^titEHQWXyntf z6{q%^E@Pk6Ve6)@Ssx?Dw4u>#N)p2=lk8WkbGr0X?7En=quI$HE;dKD?H7;W!lrWF z`P@FSsk)LFE*0OOJyWvopd1)>UEy>M#>RTZPf;-qAMZ$*>?nP7)pg*CyRcQKy`i~@ zQ}4(I$IDTE^OL7A)bDJ{h}_-GoZJeQqB}LL#+ZynT$AXRdsnf&_NOIJRmBD^=#bgV zx|f}iKnWK|=E_GH09V(Es}sY*GP^E@@$KAv73{yD_t&uqOnLX)XAcy+KlYB?`}%MA z0BQ=)UA|b8$k)9An$13m=KUe{zrU*498dbphqpC#duLLF-OHP}4ApUsCWvdyUKh$L zoTaKi3A|ap+)yYqzpdGwI=?JvPq^ zn?`J-fE)iWRU6J<-%lJuK8Ga{l27o(BA9&9VOD%_ZxPQN&u?XKeu^8-2}~g88`kcn zSgh50gKNx(8Q1@xx9<7p?1Pf3?NKfy>2!_lz#9J)6&xe@0`eTZ%k*pBO5Qi`bD={NouX}P?e(1 zzp0sR_A_1Y4>U^hK2s%og*GO?tXOydk3K-$sb!7tC|s##oW^uE3?mCvQb zPeVQX*5L>Ju*W@DQ-}9FR=V?AQJg=S1_du={yL9&G>!;zWupC?p8{&xY+N1FC>G3n z`8j=IEdE)-#rY$I`?*NDfblI=wd;OO@y((Of0#w6Jo$VTk%5N3n6jEdI_1Kk%wAk( z*g{P=4F?@f@Lom}^W{$_gsaQ``O2OmT(FjPefLd%;K|&Phmn&`;Ncb#ywjU?lg?Ws zn?r@iR~gfhcS>fwq@>iX|1Yc!_j4?b{4O^p(ijh&4xX+EC5^n)LKC-5sq zqbt`oR<5~WSEf7e)xZD!ID1&@dCaSN`-s_hElJ*o5cvc>G=ZBukukF^qJgw`?+l{p z!HgA!yEusvMK3ny7Znf1eM~CpMU#6=**#BVS5SDk3!s>Shn`We1Whc<%4b%I!>(TgL4*M51fyJbKKt$) z1eNyd=}I7gH89xz=5uLmPZGg{h;5r(uKMC=Wx>%Ku`?c&<3@MqUKFC8&x`}ZWhX#2 z`Om&{Mw-nfw$w%p#@QSWO70qweD-OCXg;Pi7y0D<~$pUKeS~Y3AY%9p=6I;h4 zk~$p1iDnNgIU7yPuKh9TR*Meh8#j=gj!gtva9g!|8lQjM=(>Zj`s!es`-{b@>F6nP zU87MN{!eyG!zYK_UN1D~il%D^+ceC?JYCP6J^yC8yKwzYx2r194_$L$$%}EgMs-E_ z8&>s`FB}YxlOv#*4^hwryj;n2|d!YBwii+Q0I95SH1803hFvQQ%DK{ zkg}^gP(;g$J8xlNA4V3#P>k)zv&AL5ZKy}DeVPy+AD=sW`N)UM5VuYK^>2KR)ljs1Z&Jt+i4|8UohAj@_+F+JUAEe!bm z)3-JgLie5|$<@CBxn{4TBL9QOrxrgOjT+y`AAR_>VuaJ?`Ev0_*X+52q*0b_rzF#7 zUfseW&(T|yJIzwg7G~n3P{l!-NlB`xU^s1+n@u_GG&5$xCND9as1Hf0+q0tC!J61O zSJ@W}_kQq)4Nk5!v$3Bz3aM9njGM3f_C4sk_{vw~bAo4!c;5Ejl%>E|jd4>%C%$X{ z*>|ZmA?#VA!<+~=)!J8_;FnDhr?xolD?D9=LSg$JFyRS%uCjwUyELV_(NA2AsKlD& zXitiet_i<8Z{l3FB$~St%5ouw$T%)N(Ih|14K<0+c}6r>E(u0~vaDMr{b-5M`nKP7 zXoTzbpG9O38EZsNNyNq0u8?j8AMKg^s*fP4{C=Ogo%OpP`k%QB|nhPGSi~Z|$ zhs`GmZCCbmEnG^Cj-_gS*ndcON16+c&Z?r@f>{@l~_n*l;O zLjhs5oHO;EG&0JMPURZi~o7xb3O90}`A{wlJ;X0*+rc2B%?U6aB8 z;k?-tk@@`QHCpWhe9TnuhpcZ`aeTme2@%=u~h7Rym)< z?*ythuuW!l=XDljGyLq#q|mK%G-?K&k@NQ>Y=3WijIzF?D$kRPghm0QZ9ioAc(#E& z0Zh*WgMzxz->ulqgI$K~Gx1MBY5LS995jSLtZx~ znjP2e;CHnGck?Xbv~9#ffgz{bd6f4E>*=fDqhrin=MV+m$-1M5l??Y2(pMSwM?Bmp zCA9xv-=xIdZIsz`mGRPx@jIse0iaOG=>1JurDN=d3}!};oC!TZEpk?8iE7SGy!dJ} zf!xnMg5l#m(pb9|8hNgd3wyjbPP*3+Et^;tF!nL8BSPT8aw|^l*#7uW|4j2QwL^Kv z*v>Qh?|)H{A6;ns{*!a9#CjEZbL=k@reJKNup#^RWMCt9jusx(IaoDTU3$?jM#A$uWyI9*9ECLdzZCR$&zKnPCE_NaImto9RE4RMgE*1c?mClQHv~I4Z2Hp`pEV`!OeaE z02H-BL91~rWyh|XU@@V;OO>-uXF@-T`3Xx%3;i<+tVS*GqZx&p!T{>m$Qu z9=9?h9&q|O4}V7g9X5i?U!oZG?tS=88&CurP{xW`Z7Ap-k2w)6GM7rNSwL;YJE8lL z^!rsw;*&j>&Mj}9@*o1_wOhKg{vXcXKdPy7-S<7?>~n9`MVAIp6G&k}ASHsp3=nQs zDVznEC<$8u6_6BGW=JZM=xWkcn@Z*GTf_ur0)apR8B4bYq860~6itgT76b^%Of8^7 zK=HIDvp?0EwAxgJ`*8lcf9`e1uwyuM6q9$p^L?Km-{0QV|O)RcO!<%ox+xuErDf&6Ge@q>ZR; z4E7^yFmInW$;r>|4E948IHs+C-OKH&ZLGnes|Vx2Rs6uDB%8QM+H_ZSyaJ$f=QMx< z20jzv^e(Iz>+rKksn4jH&e98G!JT(-T$tbwA04jE0LTpfC|=NJRUB8T=8V)=>(1S*y)ik7wZX!!@W;2pVN;aMUgzUICMp zEcq^C(I{er+^{iy-W!dAU-d>ngC#|#5ul6ecV`5T;Ag25n4<+&@5(?ZOFHTAUcg&y z1D*y0H@(TWjE1B+&F&S(xT8OqCIkE4-S899h@i2pV{Oiu8dn+8u>nq;$t^n3`HoLesBsd#E)3o3qBm^MXMS{=AqKDKb$hv|#}E)%gyO zNQC3H#9~p<*`3HmB1p|U2lNjcWNjB8n{*f zzd3{0t3^2@L?SLA1s6j+b_uvEKtVSDDrDF5USzBhj_$luF%gH(c_SpiV*jJ5MxmEo z&eb}+z;1yCM~hA*DmpKz{qseEVyiHl*`nP!2Cq|MLveab^;LF6#a!!MNr;(xYTmW` z(Hs6n>U7Ijd;k}4HEnml`j2WCwSNp+8#NaJ)~@4HK4M%taFH<5y5Tb_dh8Vpg^VT4 zWm{rm*~>LrY6s#siay%@hSnqf?$=_>;{_K?QM+DLt=P574=#iRon)aFNRI7YwNcNu zi9FC;wz}`$Bgz=WZ6*ZPQPaRt1c|WU3iTabzXY^n{;iY7D)7S?q*{lKa07S*kcdwK zPUEoF(4r>!157!Fl-~$&S3N&;M0{#&pXOSB+!n$ia{R{2+Fb$ zAzxdkp9fMPkoL*TSK@$YI5>U1?A}4JW|R!m*E-%f}ynJKrj5I*?YRymE z!VznO-L-))^%J$OB6el&P|W3vVC>1!G#)-wsR#*_4WV}@XUiH38P*v8*4Keo6v%*1 zD4Ut5wf2VZdius?dIgr$WW5n=a+|@#fhk=Wlh9z0hstiuU&VD(*i3`Ro?1FGAnmr+ z@Qq+pbO1lQQGt)z1-&*om@c2Ex`8~|35wtr0M(#ZRdT3e0D^CXWv)xAr=yNKjW8k% zMFU`cB2nq!(}vjSIRk7?T6ZfQpAuNrv7%u2E|$i|>}&yomk5DR7-MtNL7@fv`6{+i zoX0G`s&})bU2bBL#O}_DNGkwtf@tv6<%RgjNJccpL_RUS6@-zxWke_10T>W*wuJZH zzX_Q;z&4jPcTd8nO%jfoMyGZ9z_J>p@n5@wIuGuttkts*0bs$Du8)bEGG561F3Xt;9UT; zr>xIzQ9;g<03SBmB(_O#&z^f+Gt}}zdIq?^{l%VTKs@v$?L4@ofgS=O7t`k z0C-8D`{YCf$$7*Pa2rT1ysE1Q2b#lURQIpU_>xVLT~KH*a>n}K<@_}9llZD?144=& z2xk`Gc=qvFK|gmm^l_~UJ4OX*X*-UVWM}wVY!h(D-4T|%{x4Kq?sO)r=||} z91nTp7}vRVLcjdfa>JaT-(UL6te)CFHLTk1Ck5M6=YNPFXf!6YSVZsi96#+$JvQ&< zs&}5BH6+SZzx=zXpv0Xa+c7mpsN4XmR^~h2T5QhM(?BeOC1aqef!UT+E0t~e8GSB~ z$7<}niPZe2*%+LhcWT$t@b2!^`AJOWBTsl2p*`E$%W$Dr)$wSEjR14bS^^PK0vBwO z5E>UTdk>uiS=|htF}Z+=@$1tf4kpj{?lsyvfvT#>VpazabZ61$Q4Sj&IiDYej5gKu z=voly)uy2#a{$*l4QV4C@0V<(1)VJvQ;Z)Z9QX`*fZZl)RPU6QGaGJ+#@|xv6Pq?Zy#eg> zjLNjaxG?9hi{UBLB%KQUK!1pz5=_sRyK>KcK1!2skh!Zu?L$+!-)A+(e0=_xI_{nB*!l zDMehH5x|)QfcBiS3HYG6i2f;|n1ojY!=%n}dZlo=-`lKD` z5kQ`vz-dz~D^Qt1WdpL~_!fv_j)@gX%~Y2Wh2vC2i#CbEzW#?C!$-pd89pbOB)Uq~ z_`uVlown%JA@MLSYo-PdOUZfP{~VFFNmwjNQx2!2i&RA+K~`+N_k4(5tmp~dm8Ltd z26()?D3*Jr=8(R4{GcXihJL0HamZK*$B`q;E&%;1jKEq1U9{j02WUPV zWKdDouNz|vv8=WhaP8TrMF`_(y?E`mF`nWIbal_NBLBSQX<3o%>T&W6zkLio**x*x zpDe_6RwI8`J~%N=@%pgx}=n${!TpMj(ong7tO zn+osVVg_c_SQb%WLuJkc@V;dNxN0q<5!WFm)p8T!8wD!Z1XUC&$@ZJ6qMoF2OxP{D zN|Gqj$Bn+T5ejgcr;Z!z)t)*#sGz!Y6dHB`yp7@zHphrm%V+z9=k@W0$BXg{rz;U7|D@ed=B#2#9t}NjWER zs|V=EFkCn3?N02`wE}3#s?*V!fSH)z!rb+G*!S04`+q*@>=0^?Pge59UMDcA14i!W z!wMZi>qaPXSWGK^WMmWdXKH3SBTj zFoSbErV-?6A$|&wDd*adwJnVT-UvZ9C7NgjPzi1zpFl$8Ns&S4d|8j35Os5|CN)lT zoZA**=fb_aziHVw^~3dQY0t2QI=5pCBNDjmk6uWI@9lX1qTc+|-wNqgurj2zfWrM0 zN#Q<_J?7M9+UReTj7czIwX{0SEZFMa(jB2*t{enIbLp)Hl8{AoC(dKK+UqsYfMHqm z8x-jXv4cw^3Mi6QY>No~N(g+-n4CskZP~F=zcImODc6b}5_V&qJGD7I+GJsH%OS}Q z_pFP@6)<+w)<+d3f&3Y404Mz!^Hilb-c-Aam9JBH)ow{zi$fj)e17*9fc;g`ra&S^ zk%XjyQUGRM3gKFhrORIzv2rUF`Cl}3>4-&@t0ZM$!d1MSk z=6*oikONl6F)j}>03cixjAw;1rvyda{o6at2QYOe>5|$>V$c2uSmsW7b=#K@UiNNEx>uTO|cY?&oWE5lfxKd@oc zf-(unF)x>|urtAM4n_^iu;2zrUD8XNkY=5T0_L>NQYd#x@zP2Mlf}fjkn|O%$8wFr z>OpGE|Bv+puv}|S0P*exMzNH|-Q{dtb{4TJT?<=M=JWNfN`6fvD5hcKw=R!ZTCMo} z?yIe4H5s5u3@9Ql0B6uhCJyD<$v%e_r+Fjx0K}&Q;cD;jmaj|_{|Fx56m(jZqTR9h zOu}W=9m%ylFtSuCwJd9w_1gz21v}K+JAV1wF#isf-AxOVi)*zw(&?UpiL<(VFu$`& zt{-m@l;jddTr#-{p*;4UXvcQH^?CW8#7s zABtSavG!V;SGAFp#wP09oLdiGy)a2Y%OZdCCRelHJmA#Hz2~+u36oDwWF4KX*>%@1fCm88_zb>Y_D(7%}qD zbO$%;nC9=c*j;-p@YhE zrp}6PCWDAPuX=<9Aa{4E-N8ltwHQ}Ry-hVGe$*-2kpVW?APg5eFt;Bpnwj-WR9oZw z7iwZbKK|g+A55jFPHiw@= zT2hs!#Y*dS^Y=UXgg2hRc9DvGj9 zt$mWlb1Xl2UwNYycFxG81?P6<$l-xU4mPk4K02kV+BiOBVFpV_&_4XGUZMt?ne^l} z=??yYXSjf<{l&RNbhtnJh;0FduW=Ja;hWfm%yWh~_|F4xTKFH*gBLkXb<#^pQ_(?? zAOXT-8XaB;Y42OyJ*d3?Zlx!DmNoApap+f+A0ko3W&9*r18Oq=8%?6VlFBvdTaHMa z1IB)el2-$FD_FgcJ4%CxR(~b?c0w;*IS|{nwVY=1yP|DG1UR{I z2BmPdM~g~EP+XqXUR$CSI*AB2Mo2vl!#gY=nUMESdx$J@?j{MhdasH6A)b z-g&bJ1i6I5IeBVDtk5_cWx0D`iZ{vP0BbVcyCarT zvZ-{a@e*+aif-W%EN*2ImrtWfvpE6#^G~^iiu9?hgN8`DsA(P7NsV=vdzlR|O>ofj zDVW9?qM8_-t{=CKIa-PGloQU*TE%`2v9m>XzLjy1eMULMD&3%->FzI!fq7Vr&AJ0= zyB9R@Fr>R@kU{elc?2_HYKTOjA38EH&bmkzSOYueEkj#*dUat&QU$DlMlcu@Nu8y@ z=6_c%bAqR}fHrkl3kAkbjXJ)Q29yuHzl_xn9;7|xt1S83T9s~8Lq87_E?|xOHqc#R z3=HPSM1voafs?2NaC9#mjWB#0GPp4~m4^kV9yzVK+BY~(K_B=~g5(?8Cahy~AZBW! zXgq#y!SbxDY77R$d-rAyevcRE##<6<1qArLy({@J)P)9KkCW|60h z_`6sDY!wGWNTf?wE~>N_W`&=Y?y{%m$*#uqOP6yDrFGwMF_U+{QN*U1#O=^l0Ie26<1suSEU6u$P!Dj%v>#x(;D#4j& z*e8OCT)ZNV#bR+l>Lt}`CKHJrCefsyE5fAs{D&(M0nm|b6J6EiIQw;}#o1$U)y)r2 zUZexXPr#B30I4lNrXtT|-tKf$cjk@A`Ks-a&pxG!ZO+7VNGZF!7Q4KXuma8=n%5djf88wH@WsU@h( zX=FqyeSjDM$BQXRmM8$~wH9QuwSRlbRd52W4-Yz8z?`OWrQ6rdA*R+N{C=vfPh}n}cS^1|v16;=7{C09hk9;o%fugOfvt~!*1;r_vuE0Zr_GtU z>NX9XVd}+XYRQ7JFRHL?r>Go8+^q;CTPOcYmYzc+bMp;1<7*^P#l6$2Ji zx|#=1T?MTxZ!IBXMXw82lmI>Y$k>v~TmuS-u|Md_1Kv&xw699NX3P!NW!aPDypc+n z72{l8De}^Cz>kRHukEaj=!zBr^mYU&862&#FIaGOeZp-%scLhHHa5n!+t?L6bgDE` zl4iz`YCX9_PlM-rn%&5Zfr1A3T?tGHUM_JZ3ap0RcO5vrYt|*1Qz5Yvf<2}iD*!wL zY{>aff+{G+A5+FRI<_jQLXH!0Nt!Kdy=8W4o>RgQMa~cl%yUtNzuY;}yT4@Si1eVN zg0D9}7Vly;40o92J584m-ENXh{K{lB&~xu4<0&cV8$x)_mggR7YH zb(-Oh8)en75A5Dvn3jHw?qj)HN#!pU9Q;Nzy4WM2ybUA+KUAI?>T31(>C?jI4%7Amp-TwJ(7 zW|+M@C_$s>ry}=OQPTkBcEWYFmEzP{)Zq7OPG?jXtG$O<&$gs=9*Iq}X!ro|&Yz=A zv#e^SzQxTS>NS)WETeffY5Thii0S3%C7PyhNa z#11sKCjsA@*Xbs?C&R(id^|)a?=8AFe9H(Mga+qRy``#9i^mcdIp7I zN-$q3=X^*NDe#>0AXFER0A=0SZ>Y!n1&r9>qGNd1NtnI-X!z~bakvh4lY?A*E4y+T znErv75qOS=WI!e7T#aFFZNv|Z33~H@0^Ucx{irs(soS!#rS{O*yLMm3 zx&1%$uThx2GO7HOH$slZSb{V#SeQx$Pw3LC+bMob$Bc!+G;qaecFzDFugKJe!RUG^ zKJS9a5ZngpWYx>DqX2$*dpT-N2$q3y*b0nmP*aYbXB|6AE3F|$zA@I=8s?x>8 zj!KM)l^A&%4#GMO3FrzRf$=RIyHMF3jpMu(KB$xb!kkQ}uRySmr*!f)*G}n&8i^T%=kZWTtOJk=8v7Ys^8WE?Nq6b-np&a?PRh z`dZ6C8&aw|bnf!@@)mQ=VuBwO2hEOzxbiD?`EOr7czesHE3tTf%|b$c;~$GZeZBBr zOsMlN%o|J^Ns%QNDGDHxJvtW>HYw#hwkpB{T(K5$UH$scc8TetO9z0OO`cQ0Uy)28g{u|IL?ScaJ0GH$XeQC1fCcY5iq8vLa#+wR1h7nJRe+0_ zd>c{}ni>qozi&&m5iCZPuK21lG4>cD0kt&Da6=o5WXp9ftt3Rq(@P6XF#eVbvkEYA z<=X&7zCjPOlvoTG%{s1szfVsVU(eUKs1MFb-skH&_9sdwuXPXokqDSd;Xr{L6GY~1uuVF^@;wnu8{K3yfVU6;T5v~$d!T?;4MB{@ zSkDg;tybt#OHYbd=Oh??ZLshD3Vy+uEL! z-XEXUdPNOEWAt?C$mFml*pWE30EsTl4WXN+IK6u$s?mC;`v#>k72r2O zwwXP$x4KI}nWXm&-0_wkaY)iCGkkr(0x>3REwqA3%=zLH=T!KQ;-p*rJmj9RWQO|2 z{*YTuhb#3eRqm~?nm^lR{IOhdAW|%y69_c|(~%>bBzElJinSQX3rn@=YDi(xx!6_l;CX1v+f5scs`C955{*KeOTvjwt zC0rXyay!Log=D7e;5BOA$p+i%W?Mm*nNi;m6Gs1jf;wHCcZH~pEg}jUzWTvBWVrmH zl{wjCg^}X?Kt=~w1QSz~P{Jr(Zd*(^rHj5U)~ek1^jl0VB!w(2kea_?#?GtmpmpkK z1)<|#g*Q`0Vm~XorFOsb?!EceCtSnvil%RA?Tn-{0y=WyeNi(|I5jK*0hY3*O1nD6LAT z@w7C12%i~Vz#lT9ltma3?is(D-R)wYb&4@$v$8!?K?$TPjiuv)$p>7*(F@o$qj{Jt zsy$Tr#g=Jtg=ZUy!7d1=wYNxn{vPvrYwPJkw!GSLO}U{WY^NlkZ+TF{n7$BujA_~F ziz+!F<$ebY%|X=h7_NWtJ;6FbO_ z|I)(MZKG{2Im7g)qv>)?fU3%qk+p2Iz%N<${)i1X#ZQs_dNRj5!69POH{15#n2y5* z-Lz`^MV6#Nu0L(lUl~9qhBr`RrjiVW%B_Rf7*a`h9Y}TW3^&bI|M<<2 zl&^&OYooJS=X&EhW#T>&eX=&^_b=alT7M#E<+U3xMjxMeIP&q+rOdh?A1sfWT2{Vs zom>^?tPP9?2hP@esy=>i%~bXK_iKS4&#tbl&ZYeRV~R6=^NSBm&Q9JLrZft4XoF8= zH|Uqqp(EJ~==H6GS=IJC2P-wH!lO>7%pYsG&&LHj6xAx_3?A)yfKEtBpaS3+ax=(e@PJf z=u0YmK|cx#%?Y~qE>(yLI5aTA71|wB97T;?&}eV1Lzi(9EHRO(StkK-v{`(L(&*Rg zE|AP%{~Vf!%hkab6kl6rfL`^k_dpUPOBd{^u_-KOE+;LnIxJM8Ma138qSu!$-YM%= zHjxzRC24yD*E(b!Zi~@3z@Uw($Y4sRJkrC6Jfu}y88m*<=EsC;Qx}c@=*y77{EO~h z(2@v zRLaOt+pC_ppM9`K7+!hTR)4Kp`Qqj6`tOBzRO|@!^CGf^IrR0Jn2O}3xXiTuMrrO7 zm^=lEyIyp#DAF9FV3=HWv0RF~uQ19&Mrj}vJ8R5T*d62gV4)cdiVQm1<^ToN!h2<;Aw*5M0)vI%E@P=a zva;LPcACvZ?G{H}EWzO5B@QaB6GqmD$bg12Ea_2#tn9GecOY`iNSjs(J1x3Gghw;9 zj`l_(dncHI0{?s~s}>Ap;ii#K$ z{p!oilTTkh{O#4#dRVxX)5HPjONO~Tncd05IE^~Vw3WdeOX?@wsR6YTTwrWoBMl2F!2xj2 zDrWhZ{3I^y{AhlcS@16{HjTy|%<@fe3JSB1#ULHTOcJyK3m)CNB{sDIO_O&PTYsA{huWP@TR=xTyP-WWw zy+l`Pi<`VPDcI(sPu(26Dcg0~d((Z3X%JY-7CBmF1VXrp8n4^Y*BhxM$tknlZls%u zrMs#X6w=^=H*D#SrL{|ZpP~Wx1Q;! zU5|j1T*4C9kkvN(j=|e=o0S5zi z7=Dbbj9|&TT$tQ0bxv`9YTSd+AkJeM01(s6Mza?zs+w(ZZX^bDa5NMTNp}nd{}W7m z@_?a`f@&q|U&>=EGqT1Si#sVCfm{n7zR?GWMd}NpUO)x=rUQ-Dy-0*Tsx)m!yltX%!vS2}xi5Rco?s|c>snrY z_twMTZoyGvep>hF#mJiH#mL97{BV{`ovr`%&9&!!IWI>?e*b>EvU@X2T#{@Sn#mqn z+jhm$MM;z#$8|;*+ODAnKC{7fF0nhB3zds^h9q!Y;LRaG=ODX7k6vS=@DTFwcoTC> zMG3Bjnag2OYdBq-EifSxJ~({%3=_7QPL$0Q!>=Qb59!8$ZKMxxeuji=dYvY5uHw5J-FxK8B!+oLls#~l5vGw;45 zKE)q9l`6;le^J2>J>spEZc-+gEbQqw81`_EIvC3veuFKjBuh7VUq!PZwoQwVlaXx^BhQxerCf}4tFkl1IFlALlr^q{D$#Rp>^u_yWoXTpjPlGEm z*8S$JfMd0DAgm(=_RCJr>2h{ya5z4!c*B(Vdn^N$pkdwVzL7ST8A3s>hFYf~H zC3VfxEdnTR;u3}Q(?Yefqoud}dZDK$UQzz!J|||N23%iCOxVw6I0T-4yf&Eg+dq%3 z@6Qj{j;_uH+ecUKMz00?SASXit>C`JSgP>OUUtt2B(g+FrCpY2AP+I+*pQO!23_hE zX@nId-ool@N>W-CEfT?drP&#@dKX^8=74X_1t`q2p9G!E_$hGrlO)Z-38}RSlP%VV@}z@u#2aTo3>_M5u4^3Qd~^^{#`C zX7-Z^6b&tr^$qFl-cZHH40Myy!8&JrRJkPUc=7Ol&f0@xtIZEz5pXgdpWZuqbM3c4 z|Ef2o{?}vKYjBe+-_S{2QylOgD#J*RvR%X(QKWq1QkKjqt{&fghLhGzdUQ1N`ZNa} z7Z4l50!J!5n)~G}zggF!RPwx;;B~g8yPC-woJ-$ULKKE5rmaRe9UGO- z-f}HmJmX0ScQ8Hg3<1Zt$gYQDbu7u9H9=SQFdQY#Z;M%r>P3e-n!QZj}s%`wMdhF12(;IlTF%}wLi_2 zix+uhO-({L6>j8alYLxaA0WqAboszVFQhGA2_CS7UTB8hAx+rAgh%bqr&VAg}3JOrPsrl+{S6W zizW7_1FE+e&x(Uvg*P~?nsL-sqlkDJPB3aS1I|L@1wpqDwoMiusz~RhdEq2HeXjfr z!7#w<&qtb-&Nn$)HtI}8D?2pl&C^y`uq^?43M=@hy^uv5hW8Ug#~b_^oHHD(l~x_P5d>|xuBA|1QrQy&SZV!*MR-Fe=$ox^c<%tC#Hc&%JP0A>*|qtHE89n3 zx*>RZIP!2rBlyh_<}x<5T{vAVsmOUIUX!T#ZDXTZTCg;S|_>m-$U7Vtil8GaMt zcnzqCy38~uTqoBVG*$|F#vjS@)g%M^NLPw%R>Xn+SI1kAMADnpF;i5CL3O28-31+Q zFx?f;l5Y$giSnQSFE3Xu1~;roEBEM63ka3QZoy|#PZhEy_KUhq*f4=qvDThyt&4+y zE#~$DZQSvaN$r!qb$;%av1CfSAz8eOOvG$!10xA)$xp_f!2>-Y*ut#pepQo zD`RgUwiDh|Io_wV^)aHA@Gk{4FBTh`xcVFpxEIcroV6dSUIkO?;hxQTr93C85LUoe@dy^f#~1Y5Wtp*^g#`0~%#PvX zvM1I{uQwMZN8Ss828#Ww`zU-DRC2u2{kBQe&_y#8N{H`{wx5>SyuxjwIAVZo388Va z$*h>E4NG33_mk=hEr}z|3pJtlV60R2M0_LiMYpR>)UMeAOjaW;r8~wg$KQnb1GXR7 z9Lx%~(TFx+>8*p%rA3e%%9=iKmyuFVjiLrTb}nEdG}@W=cvxC(ACI9d|KhZMh6 zqUv+9hz$3t<3LL1+P&ki@mq@eb!2MTz{ zPO!Mz_Y5D<_S)=+IWdC*|38yDT=soHWlv&Tdxt@y>3j`56UynqpOYk}3XuC)o zV%mEpWG(u{-+un+t^N7a*C?}k8!sJ@Ltbv)fS=eK0C1X?!%RPuO6;V)&J%`_RyVYH7;Fw z=`EAxiVr%-wubLJW!^R(UiA1&jZS2P?eZxFb><~MqO%N)V`y?s)BvN}8&=aPLf?DS zKt3hSb-j1iIB)>Z@*0mLaJFI8Kq!rP2U{se2fbltn_gq&d8lC5(k8NRP%y>?zhA04 zYlk~aROwd-^YJ&~Vm^9*8j<6?WCYh8A52G=4V zX|h!)UdD(|bUF$x(i+p5S_>YD3vpOTyArAqk(-d_w`2?6fddto%t<#4u{qNpyomnz z)zryf9|l+Ys=TMiqF0`+PCX2^KU{hIbR}i<%+Y#*Uash=hmL$&pK)2W`^( z#v}H9k3*d|#nMxzLhZ&(;uB}W6lBE*-Lnoc4-FK{p~I8BCy;C?^fV;1&@-sxy*Hh6 z8;eI-YwSG&$QbADS6+@=Ep8vwm!a?Ju zGbq(Mp0rXvZBT>rVG;AZ9_yoaNWU6xF;yy8Yu^Pb#lEQvIMbIdTF@4q!yHsCmn+qOc4V zAm)@jn*3NCMVBPg41y2Nw(_DR$t8FeAD^Wz4euW(EH-R(HSO#x_t{t0s?}ih%OAD} zh0o5e4L)7jzV_2wQFQgry?gSK-Qu`Rf_apW-erYDl4c9{Ut^?io^t~pmW7_fQjZG5 zQ1QiHOu5HF!~@{MxH)twE6F|1sbt;2#o5MF88qVXHFrEA%%h-ECtYIm zd|WW7^T?i*40U9V)2~fGzkB;7cXZ{^!`~L)JxTsJQ1PPvx4moM-B@kOc?H#6sA_Dj z@FdVSw+kY~w4u-85~CS7!QD};9V|mJE3tv0sQLb=nmM5&aag|MCw<14DR>j%ObAC3 z+xO}f2(s(`jfy9MzsRM@Vt=b>yl$VH#h%5$ z%%h04xuX!@LSq?1Oa>~=P{c9)bk!J-heitB7OB&bik1+}c7v`^me=D^Da$Y)i)Pv@ zb-Bngs1q+_-GAo@nT8n`Q#))oJ`>gq+3w4ut<=14Qcf;ET>E`{ec;{E6-7=kJ_lQ! ze!4PU|9j5I-@aJ=Uq>%yc|R#K4W#!0@3`33FHP92kybk6t<$}|<#3R}vn&}YO@`=% z4rLQB1b24G&$=}x9*kWT2LD=eht7h!DghXf%hFCjgTSV7Q8ji>HuSeZ7b!N%yPl?T z!BnZu_1%S&A15m#GFhomC!s*!i$$i17yiJ8#e{ z0#*!|a90I);|cU_d6R{>$!DRkbP}Wu$^kejuj@h?I{wv_2c;}1n?jUK8W%X{)&q}9 z^bDt@QMm`GsSEm`gR0Dgs@8;kxNEOB6!H(KmO|+X;%QjY3-5_&;6ch@`HyO;HAKv0 z)u z1>G46HEH}H>nXZCK`^J>Pr%L5Q*D-_C5^9d9p_*lhz23$isC4~u5 zA5`z=@uhK%-%IQktjxCg!GMrA=$-u78&A}vi;V{F0##iI1~IzhpnERj z4+Jd3Pfz+ke#~2Qr_}r4*5poRj{be^^jaYL)$z5Z<8;FnK4REsVHqfQXQ`eB|2~{I zk>xAAEKT?C%Qkjg300I|-wM4R9naQ3Je|jZs37vxaFU+l*)iBjR>JwshVzEF2%jF( z1u;3MWN9~Ao!*B*oi22+P#h_S=^Kig-4t9eoi(#LsIjF(n15f*2*7!TQNx-UAXUex zG%M&ko5TtcIn#~eJQNa8CR8`GRgj4Kk5>AR#t|`YC^4y&>WP1VsZ{8=O1BrYQz47P zQeE-ej?2|L(IYx^GGUGFjrQ;~rU5c>b&R2LBO*;ktm$%yT|%H{Z!U)r!=<9IIjk<@ z1>I)irAFG2;p(NZ)WYQ?=Nsetmq00&*oQr-$~jEH!X1a>hB{yiD{lA7oISP+GVRcN zu2;*Yqd&XW?tMG@%V5rPP5;LPcXB-*8GpDk{_yt)Pa}Ln=lJDTk)FY)AU6F*Szp~} zn*zmAEV26i<8V;v_D;0&_!PJE#f~Y~jcz3zMl??FPs9oP(0!x9D2i(gmQgtNe;A=) zo;>bAtkAKLLM|3+8qi%RV`VnJHG`(!&YdsD^f<3=ZlyAeQ+-M2Gq98sA*s1#z4B6k7tF1LAw;H44=xh zH`~p!^#hTl=!$7%5UN6aRvI^M5pY==<`Gav~bcz_S7WgevQ2jJp-k%q(-|% zO6DXlz5Tea>X#Rzs}ucoubTT$>Z13oJzM*k3l~ED?3D+5275FOP|&z?^qZ?D_MxLV zudXx`joCX`b_07)RPm(!++r5g2@B0q%)8(y z34t($#qD`(y;jwr6FOBwt$nze1X<9SL+lk}k&#LYM906)R{cIv6={)rPdp&2eT?WX zc~=YORc$Dd=WG8K7GucrzDCXzHtDErIH0F_7mZs7i;H&f6AVIco6Bn?@wA8S`S#Rq z-WmTTQb*<^`uI-EZbBG#oy6hbE_ar@7f(%r;g@{2|Ha8w*XXkwC-1j>I{M45wdbSH zJ?m=#7vj>|>dS|(o=5NXgmW$*9Xe{AK59#EK;yXn^cyBGbJN%K&1&b(j4ATD``;cM zy2RhFn2slk9VRsSN~kNoz|qhO*vSM#=Y?E`#z+&B;iTAq#{ZXH+V`4wc~q_TR_ON4 z3U+uyW1iCO;kpZd%kzesjR{k#x=}g95X*L=&ezzTqWokfL)lJc=Vq&*=!VGEtruFU zh3Z1ee|33unF@ws*TBW<+$h2kZ^%lwBv=d1`S*8bc|&PC(xWd8r5i%aAvzm&xi)n1 zRE5bjs3I75d3(J~A7fVFM0z2kIw#4Eau#*UZeU~*DiPZnuH@Sjx=zo8(+x%gyHlEP z!j7%{c=pwsqdz}d^NhaOzB*a8>i_nvk9#-em8oi#xbb9d<;CdIsZ-|#&d$^FxM>Xu zwZxj_?YtpiFSO|l$zECB=24ycb!ys_>+PvZ#a1^@Ub{awt`7|aS z%km|p%oQqnC>i$RWX}!|&+zk;1E0-+uEemaxxLyq8rWK|9rd@LedU}V{q6g+H(&iT z`r&Bs@rgywgCph;WqG#I885$oO5Vlu4IF3`2*!;`8vk7&aOeYw+(+_Jy)zbD==t zEkv`K@m!pz>^`n^yl}oq5ZzyJeQFzrPnp<96P==#62?v9Ai! zc||6kwC6LGa`2p>X?muRCe%8LV)@#JEKQqh3@=)~j5d%E?m@!Qx+NnmcRvx@*%05j ziPk*fu-J#I<Rp;L2)tQGco$z+tz7@2c zUHQpSzZy7J_t-W1;>y$2vkymHuazY8qU8GAD1TdO>}GY$#&Z{C;-=P}n&L{QnCEI} zpibv8xR(UZ_h*uHf>>s5F3V7$O~%FEc>8dif4@zCp=`S^R7Z>3E3~B-=X+IteWq>y zvOd!XrwOe(-%f>c065P%Tq=@|mZIFQGkaB}|Ch0Mk4p2*_eb~s&2LUFO~!ziV5C|= z42Ux_pv=S_uSpY4G>w#m>aL;! z!eo*(QKQjm@;qh>u4~j+^W^>Gnwkl=X=c>3nozqTSZ9w5P!V0UB_>SEN5h8Fj4odcP<$F z&rro8YLdV@%uS*~aDdbrnF98xL{sFa zX24`*^$4qY5WB;qWn@Yfpj~LOGAv*U7eJhiVDDa&eYSbuyYcbXjh9htuYD&xuU|j! zdGojEji>W#TVK~quD^NGHiR7w&8|n(hOrz1aRX)7E%aI1e)wCEUDsc%1_NEKo(HFM5_};CRc$# zQ?$xlWxjoCVznT>j>)ufSt_&6CxzwVYxLGgQ$j+%PIzT8U4udn?x3>KtTMS-vrIzc zj9DlN#gkbDFl6xfuTOvDyo!1yUPa-N?NJ4lGXLnP4fY+w~k&|NHJpa zSwRBTeC+F@5l&FrQyQl)8XXr-YgL zm9}yFU2!P8enmks9oi?g^Vated~hlF=(I|O=6(tW1-&DUmf8lb`w=uq)>Hqx3-W9R z3=Y_yTm^_woZ#4$22BxIuHw!jjZSHf7_~;)IiYxFcWaAzuP@MP8A1-KQ%Dk+*sHY8 z37EilkH+Tq%i3P%Su}ltrur3uSP^*o;tCRz;CtV0WrnUdxp2Jo?5(YmSMDF@ja$dp zWScWFZ9A7kL5J#{rR5XS~S zaXF4$<;oRh#;NdVUKJ?BU0=8NSyoa|1S9mVStILLn!gSA;RQqUH@_FC#EQ@m3cR1J zC9nS;)ctK(DZmEWZ-By#odg0-0hlE_Bm3nHmsOoDssmR)z(#=tp(jI)q6@$#v9>|Y zX4pCp0X2EGju>D{lQuEyfTD)_r9c4hZz>PZ+3q@<2rbS0vk1> z&TN6djyK9T9tsq4B~}`pO-G7V{p4uVzzRN^U|h+Gw{Mf41u$)ngffucv3vz_}bGAG^bt}Z_i&bBNzV^f*r@)WNV^D5RLWii zFqDhKc~xX7iPt-?PE_~#?Q;pG)^woNOqm@p>mZ5|qGZ>PpVs#Qb!MP-Bn^~;xPdd8 zb>LwX*d|85)Hdt^*fX$URK#l5YMmY(YqzQ!;R02>5hqReThHw*fRt!lG*ueyjKtSo z(c|$1_Ub$j#14W8$gh)(IW(y5?+hiL)r*ztQ8?B>j=iCc5KVBMF~eC6SQL?63WZWl z!*`hQIMX@p!Z{*mRs@UvmN4HXrZU23+8zlVUvlFl%~mzw+Z(xMv*`}Vvg_G9wa)?A z?)|kl&XU)U!J17x|M;nHqUJO2`joItqY`hCRG{&6BU@lkMu42=IPN+h=M)x?%-PWi!{2{UwY+6PQUl_t|th zWFXAWwB1)kbiVu@!==YdF(!W#lZ219rKZ@!%69U~Z$yOssV|;eMS#=Qv24@fM|a;J z{n8~mZqn+dpr0Y$c)j-OZ0%FyBr;s<`!;mcJ_|Yd-03s%s^l%0;hPYE@Xozt(KKSH zXuLfy!ddy%6%6hRlk3bMsO|j1OjFk2%7j&)(mBfr74(Du$B2xgb*x;xR8HnHEA5qa zpNIg4MNp+3V!#1(mY>4&-jlT4I;s2+t;cRS8i6x{tOSICE=Fk2JQdj^q0A4*hs=+{ zxNK>aS*_l>Q8Vdz{dU#f zcBEfD@w!61FP=DWhL_#UYC5};Vk2qiBaI3!u6GYO|8%tPRQi$vpHr&Bhn(-#`|Nam z2T@94{YV573uh(d{Wq7PWaA&YTF@^=OcCyJm^5cq4?**(_~a$@4UkdTIaQo3zd&ia zrpOwZaf%k@BdWrgT&DqEi>G8*%&@RetS|$RK`6V3>{ck-g-SPZES?%_v|$UGRYhs0 z;cmIuLrMPXlXEs9Ig!eBNFHf~F2+ z26q#x?;*uo{g|7KTsvpU9(e`aNdDnQB#f<+MP>KthmhP;0+S#~Zq~I%;si;ZpuDrE z+P5`>q9NOptCU4GnzJk7jX44|dU^88?o76--MA_s4?8UCSHV+6S*MY`rLXwQcfs97xnWx4odG&Y#?< z_6{Y#u08qF_inNcd&`|yb?#WoIDETh1>#iApdrc=k~4?YFIFI5GKh0%uvKK>5F^F* z`I%7W0dN~v`P&UxI$~n92u|5~QiTf~9HcmDyiV>Z2!>#?4>=?P-QLLf3|u5z>Y}Ov zlWV_~ljINZO>_jUCPss+cMr81Z?v-UXg9H^33cgPe{!9|MWz5-89WP7W1m$azvq?l zR4d)*Ewh>$y*^Q@rwlYVjoGI88$#N*tUz!q@wAKx?QN{`?U&$+870Y)svK*7Cs6xx_6wA0oH`ZJl}?CN3@@*A?ppKy zo7YwQ^W9%=4xf`RIBTzsPd~r#`fq!;9!|a*wfyi7)0%qS2{p&h-Et1!dA~j-liRY= zxtxHTTlu0!HHM4EDZYY9CZaJ%)sKfVP4!snl|bqjr&FNZq>e-npP5qS^7XvuBeijg zW9`FwLXVZFDGEmRYmZe)<Nb|r z)I(7g<%&S0px=ma_V~@nHOo1AD=(c&zk>ChAKuZ_mXl$lDjJ-T<@r@h=;amkz6^dK zZZKB;b)Rhg`E!snU7dLT;JdxGuXvNM7f!zR+4_U`r;@Kz^9UMY@0S_Y$|@V^nx31_ z>;m&Rh*@PD1{t;S6R9GFnnB$$s&8b>Fqd*k?~wCs+i-q4RnAIKj_e>qFpl1?#PX_a zC!}Dk5^F?&PUmVSxTdgaw$$$B9F;Y@^V$rE>^D~Il{@Hy5iGSj2bRmugI+Z}9*>U( zM!~W$JEzPK)L@_z28V|1CfiZ#==FA=&KO*60E%nJ9+n51X6$(m?l93|(3ASWzvY+b z$;D;sE}C3qI3UXSA-%m(xws>aCx2-n-tdxykhl z`zz$9t*Mz;pdy>LW}hoB(ntDLRM5^HzVngx;UHIfqr8r&T$$CXLF%2ZTDbz|x8J8A z<@R9@;_Oatnaz2v#SSB4t@C@f=AU01{!pC865m`lf9 zo!Nfp0tl$~Zc0T^TDlySBB8KUmtg{Ev-*#c2h&v-`F7skCuY8C1){j~j%)jJ4tM=w z1}n{=LRTTja0Itge#4K9XDL=ddY~SfiEs{^Lmk@&VBeV)d!&ztnM7j40nJidB%~Pv zXQLXOYLT@ynweF&YN+3&eS3|4MCA_SnG6^2)= zA9QRZ7`s3U*KW#E;lqr9`BE~N_Rc_*`O#rYHmVdO#WW1)UFnPX)q$*46sk}MQ7!35 zc$!Lj@5MuLovdAy`{3}nYQaG5-rL3cWs^L3V7LHyNVQvp`{8ijApR5A_$}gx#PiMi zJryRXdF<$^5izvufWEKICmgm~IaQ{CU1uF2396zCPKCSj`YFC6dO|jtRprtb74}A; z2FN)~rJqTAO+(0TF6=In9?<(_*!C+-;Kd_NzuX!gZhTL(x!-X@>g$UK)w)Sb$R5Ce zlL*RFJTTs`Z%5*tBjjIrU!ho1o<9Ki1wK`s~MR%RUo7ENpG|Y`K2~J?F=*wO*e_hL*tI zM#vY0M~nVcp~__W^%WRV$nTfJJ6qHC;j&JnQZJ*B;(Z4IW+q?gYm0QEIT)fyqpNGj zX9edVxm~%%DJg;UoJ=~{9#x%#8yzF z+Dj$J?B9kBV(<@dLV8PDh^=k%6A1P(gqF}zMyTYNq~1*SsZi`pO>;zQ8gp2x zKh$5>9J=YcyJD{s1f!d z_<+Dk%l-BcScvv7Q=WOpw#_k&Pntff%hWh(TBuLsfAn2Ct6;1?a-Z_^x`9vs zu=5>WC?V7s5KsE`*?ULrfB%YKkjpg&Lz99%qgZzO?z*@`j%np>~thM(;&vyp2curdL4h2Eu@O&Dz&08UiB8wA<9WATJa3$3Ccw^^RG#GH^V)NVcTQoPRE zjgDmR%^uI4p01MDUDviACvO0jGdp9+E~>~qUq$BH4x7$lOpbwqg3Q8xpV*6D~!2m0rJPg^WQc?whjePMA zJtOKYmXK-O12K=wyNWafcHz$5z!W}KI~32c@-oRa2QnXnX+^Ak^fb`7Y#TZV)D2K* z0GvGScVGbPf;g`2qK*JSQ3}!YVN)|qp-Mg;Er?2F;X|k7OtLPWkV8uY4Rn*A2~ecb ztk9aKEcKA*KLET$&6-Mo+rS-n$MtIXSZ(LTrwdQK|8Tvj zeTBADjeG20rL~JkwHy+Ko^m7RuH_8NbQjA_r{zS|Lz+?>FY`2BNA?4p!6x8U9iG;! z>67P3!w2P3h1v(K7jA%$W<>$*9$gRtg)U|Gf&m$dY`_od8~F;pyB*-omUL^LQ=b2* z!8s^g_wiA@FjIQ}3Dcu9~s=_O_g9?Ub^5xy?+GkNE zZ!W!bUH#>`Kd$`&Y>bWXe6~D$3}aJR|Gugg$LN?EMjQF=KXJx(-tUafBn~QB=Sc#4 zC^c7~qw+t;2-RU{+WSa#u^Er?*~S|%`D`YgHd6-`iZ`eW81TTu$CQ`AV1c=BoE;>)rY( zepLdi>0u`!GLik&Kz$Lbk)uIr^{u&@d6FdJg8eX=Hy+DXoo%Y>hojO|Ii{kj;;b(o zxk(Lx)q@<&B%@111ptd&p`hEjh<`;HVUVFjyFbqfz7m*Y$D8WXfp+4@B_|)8APw-L zF6cm%>3CcweM&=sNn8bE=D5Vqlt~835IA(Y(tZ?A#T8ii3i8};+rU>d*vqz?v>KE^ z02J&6q`pl1imM7>oAIoDrFZq5$6-2V)TJVtDWYZ3o*L0&9aNI$8tCq;lZf-l7Gjh= zx^_K!%aI5IN>A<9ZtsuY_5GwD$_I-MM3Gqs=@?d*Du4!er zKbQ>_kywwzTaJGcSZ~&4p>U-;@22p!i;hbyfK>fCKua6ScT|8d=Oeh;QqMXySog{N zVfbu+TagMnpezvZQ~*0kWUJrC(VUcqI+%Q+SCj86Rctq9+U{bB*#jC*6fep~)KxW* z*?OoBOUzIY>8n)WFuH(Y5m^TV4z$3|LyCj<(R=I2RnjwAkIPkEH^3mU_$#{NxpoVi0yoV|@6$%__dOH8ZMRqs za%s0#i1Z&iQQE6mt&>{^xAz65Cgq)G`1J|sf-@LV9qH}$L15wPV&*g}+N&g1#$!-= zp$VOfnm)1tvTJYcmRR^BMAxtxE)xh+U~qAQAvR(tw)-nIqeoiCY<@usYb)a|rs#g~(qWimAKky$9vjkEI}H+&sI zThhn4$EfV}T$QaMYw^l&X+FL*12+0`EGs}Fj$V!>>ar?@N|Xe+7+{uSD#wdb4FqRLNw3xhkyV*zu`u5Kxh3X+uF z|I_-cSVb=`UddX$Qyw>qB?eJzY%HKHAyI-$6Q5mVGG|tT0DF<%U@$42R4B29%$C@; z2a1x=Cg40%XL1}7oUFqkO;rs|=4bav7jjh3-d-=o$_-ASYZ&7K19LyAFawOUmF5~@ zC&_2V3Md^KAHZ`{8{LOQRwC%p(*)s;BHKVjSa#tjk}i&2M5EKDG>rf$)t;$Aoed=0 zHRDz=ynvd>gp&_HPI|Lk`~0sLHh=B8cX;y6xhG=pKSI1a1h*mp{!w3)b1FO_s6rJ= zWzT#SXvBm|`yeuV8}&2*{hI0^9gH(z4@Au{{`i%HcpA$~2MG{SVaU$C@+r4iYtA=W ztb<@SG9HDNlImpr%-|HDUx0wKKdMLa64`K+4l80R+gg}P^F9>kv7jv7K@>2V|NL=R zK)48`|4vr6N*Rxz!{T%r^;JF+9_e@4c31^+tk|)vMw@6pRcb%192Dbc@*dtxDGAZ_B5*#S&zZFf3kV2+WXhF*G;vL_E%55 z*bBeCV12fp`FL$U#Ro|MT?2(y00u^`D%A8P*5u%-9yX{Ov8Vay-^mV+SJ9{ZIQAl* zQ?4c}q*_uvKZoR_e%U@mnnRb{cu^*cz+QA03qCUNXQQUj&{X6oGs4uY#!+>0)Gx0) z7+?o40LdMe)q1mSyXPaA=khhdJZU$_Y9-d7)c3(KWtHQ3FNC8X`tm@ON1^6ql68*! zVF$0&q#@NvGNsi~R3J^?T*?(_OhI{YMM2XuW9|L*k_`?>nM=-;yfIaue3_r*-3r;T zC40V^Z#hJeT2F|ey%b+=Q#(*L8E6k$KbXj79>7pJlv#yiB9zZP$)-gT1GF`$g2XXQzRw7*I@te))qO@FE|EWdq_H7k~j9u)n=slCbeyP8I%n5j40ho*pj3y!8~EW$wK5SZ+4 zzset%%U1xkmrzr5S*p+rqlm0B@AKN9A6=VNU)W50gB)Jl)LxsEEdYq+*3;Il8NohR z!Cj>pg#g;Xf2`EQ!r{GwT{w6ICe>jQ?fb)Zu{yJs)5t+{lzZG(9!c{Ol-NA$kU(l> z+d(_74vx}h`$-Rx=G>$vkj*gQ7Az=`-0k@$;2wu(TZ_OY55RTS)F{_5_>0@pg24l| z_i21s7Hbe4ISS__wKuAVh}r5P^S(ruV^p7fxG)yURm%Bzojj)66_pLP_;;OenbFjQ z8t`T#-hB+|8ts7J(9_K^1wp1!RTmcw>E)h}R>U(<#Bf!d?!%~oMZ0@DJ(gY2_5D0( zKO|0o$-?by+1u2Z`m}oM=Z|Z>wch*5Tc`OMu3?4VKCp*iJ5yzvKTJjPAOPs_v3M8*M zO{$O`vJ&S=)qt>p3hITS`3>;$j4YT`vt#tTPUp7uV#i%UdQ@QqX!H6-^UAgHX;5IQ zZTbjIs*mJu%|_R*8*85xO}r4^U-K+XK3krAHT=Z;P0z-bPjM9q0CC*%Tn7=|8}y+d zt&$`;PGF`*88LsHN1Z;{5_Bm&7Wq9GsHCt+ZZb>`3TM&@m&6Oyvg0=3r>`*fbR}d@f=< zwkl$WHhp@J+B82HRH}-gu^oGSXD%Kv83NrG-ZoqSonhvJcO&t$M{~0e*1iwfycu%R z>r>KpH4~RIYKMvvT=JumPypdI!JjkyILzap*ai#kGC}(f5JC>oIALpX3dKxj@Qesq>a z0Q=q#pz8Q;GT=41tmpVujuVMlj>9u%uyOs=APF`bk>7jX1C^o;!De>hY5?x|YeB`SF(*%<@2dk-KB!I)3jL`oxBL z?M=)7bInGtx{f86gSy@Os&Hc{4^t@DW8j zvDD~L_t$%Unx#ioqk)M!M$6PL!%*)XfN`gGi${xTg;b?L5K(dx%-M_9e!d*Sd7DR_ zT;DtKe8GFQ_QjjzPXhx1V>-$yh_cyd*7fjJ~kh1(s zn`KYX4oR0(;e@(C9d%WTxgiU`Fbh~R6#>9ZAd24YB+i<_FZ)LYGX$XKeA?XsPf)W(akp#4<-0=d|`<94mF+mYXD^o4XM<0&S$M;_P5*c&e z4SRMh`DKe20KE{9RJo5Ocm9%q-aDN9ir_p#F~q**VqZAX+Pzrw$Io7L#mv!?;&T7( zJLBJSlx>us4*dScd)dD}o5|kCzWML)%K!fC*Asi7Us3n|%gVp(-W~bt-M{Yr-=L)az=Ym&-ds5sQtis=+dc2Ad8W$rLlrp)r%ZPJcuZA`u+`HWTTgHreD2_?3ou5%HdJsAOiE>mgb>T{sWjUr5j zY6&7gHDN0Ej4~o0a_wu^H61D*w~qqX_fgAv;TRfn&chv>3$+^|n;WfLO(7e?=N;8= z5^G;ng0S(@yYRUw1K4CUVymU|sjA?>mRt#useERArU7`7f|<7a-*pHol%ecadl=H0 z{Y6W>YhP=u!en2_E2a(TQzBYi-wlY16*~!8m&pzG#%&#xPQ5<&jErcvGY>3lg!ZB& z0Os48kfCt+x$Qu(6zDkP;gHUacD!4E0Unm6OI)e_YD(#dzV;wkK75YqhMc_1HE_pb zZ#UOX@ujxCg!~ayo|UWeoad9>9jLx6&!N89Ceuzs+n$EA7qv!xHmf@#CDVNg)uHwA zF|a$_w$HUj3n6YU9bo3qmB!BncX9oA_&CAV4<#D_zkSa8YX6#ho3~)|75nYH&I>3d zA=l(Hr1b+tHjo^#tMztm-+5&t-vpkipq?IHVQ%m?Gdpaz;E6Q}OJ`BFtsSZCqs-L-X`61XBab*97erRZi(CX+T#ktyjoOVk${D z!sdc2u7ydSUnI8z4@=>lPyg0PmP8=y$*y zv4M*pJlJ<9#x2&uteN%+i5HYiQ2V0#n}Y?L`JLOJk(M-FPRQ4wWuPvJkzH&?+*S>`L##~Bxcb&olu!i$Af@E$K}sWC!g$Zt$lf6 z@{Q4pByRj8Db!Ia5HBn3tw_lbPsly^{M`m)qY$$Ue5_a_ zP3m(P`mAjG&Ci+SX<0fQdi1aoI5K3e(n5PH<4cNY^Upq7V&-LNj&;aiEro1YeI|TP zJ{fAAd^3~1W$^wq2h};tn=`rVf61neOP9*ZmE+Xg<&727<=K_jhIbVi$|N&4%@J&d zAeda3cebnUOhnu%RiLEOJ|90y&oBXDZAas{lnm`EBFE9kA0A9%=yW?w<&6EaZVYm} zEaUy(Wdfg~vII+*J%I()M+liaB+52qT-|nTq)E~vo_kw%`k&;xZLglQ|K)_AlP}#PG-gG9Dp!NyksTy@C z6U`+bUBVhX0)3zL<1||N#E}%dk{mY0Z`$)#kXczS?#IsT3Y-!T)=@#EC8sIW$T0`M zRT;Z*N}zoBd+>LSYEb@WSop4P<8F${z8n@EH=bak87jmIarsXlPkNm-AgHbRu6?!) zLfqQxWpF#5L@HA|E4G~~QXv%kd0-}mN0w&C*&U|wdij!Nde5aLp^|a<8hw_JdVlfD z7PXHPK9i}!Ra0M-F%}Cjcfh?#%UU1rx-m#H2+QQ}s!BTvP}&HnOcYtl;ilb}*`4(w z6d?y#9>U@H@^p=BT605|J`%$c2<^bEsvI=)0P_|2)Z!Ngu~Z=79apkT;^#SUoptQZ zO0T2NF!dCaAj1j}!)o%}9)r$f*OwqUfmY;8sfj&EX+guqCh#L=-WOlu)qEeWK0M~Gft6L{sK3LpKGwe1@MZ8^< zjg3UF`SF6`Qf70}1;?cE}UIWT?B*@^H&!AoMJhhGn2Kh?F%E|W0PNxP+!mB!kxwpZ24P`-)|{yyI6@Q<%T=Wewkie~0FgX2D z1cAE}GakWOl6){!kuX;m5BiTy&IETsXqjGU9W7M33+(O39G9`CUJVJ-;BJTo!T{l{ z8J1tykka}c97{Zqfp7Og2^@=>#R!9_u8`#$)C2~2fwb`BpHd*lym^;S<^V+bB@7_0R~S!1^`79<|c)8;#PXU?4+}+2MP?D<}BT zciq^yxy4UaGJSNp2@R#C{Q0Y#3?(MZ6Cr3VGYPg&%eR^A1hKfd!PY3~&ao%tXA13H zfuw~Vup`iofyAAC^v*GOpCpn!LLw@#B6+-F5vWTHkX@+?J%uo(;hO@QVwGTU$w%=< zG-16Ce;3JBDS_?Ua~@(~P?uV;8Hb;x9Ze}Zu*5J9QN)hC zbCnrMwoD=NAG5z%N~hCk8X+UAz%(UZ$HmTF#4P|ikON?zL!h5345JlCGllENj=$Gq z;a?B%l>NEwN1Oh~hmmy|+XOp5=-pm49!$$u#kz^aett*vRGsu#*$$yH!l};2&&^`{ z$bIont~-2CGpO+)1H>30#=1TXwa1ie@K9Q&DW_W=o3EjK00fL3mvIh>3fBtUNf5L< zT{_Q!clvhf$gIXivsSpPRO_Gy(H3A4ZQT~pMHruTe}8a9bJ284uNKzcuFT1&9{@7?D>a}6PIKt9 zzI26y8n+N54O|B<-SO`80O6Je8Yc_vf(+ z8K-W{K5oWu(0XN9T z8EN|9(|uHd1tkA~801<64S`EAaa7;u(3a%rFNpM%{ICF_FQFu20XrkhQ*j;EJ$*n0 zhDBvH=pS@gSn~a9?;vF^_ehkJp$dxwKoqFYd4>GpMW7< z;rQaye;g2D+~1=H57JzzCw@Q?#@QcK96jMaO&_D*`L;~Zcc?h8oHv3I6}ch}0x^P) zvuqDCp$!Eiw`0#c4vo4O6;l!AeyPQ@pG*R6Dbk+hv#>b$6h8fYMNwL|Ot>a33$Fa> zET7J@E?f5l?cO>J4@iC=tAiC>TDgW|V#&#%f8{y&U#)~D*1;XgT4L$JN61%|ji`0v zRmiyj3|a_tvN8YlQhVcKCw~cO*W%pY#GEf&+C>HOfAy6DczX6**(l!tl^M1Lm3G9_ z#JxkYw7D~tcJnR?!v?J2^y0qgHXbU-k#sYdd_W z(+Tazc#fP^`#*j3r%w-zxJe=I$R5BZDw8Ok*FXO`xBIPQnVy8uq9qz*3td2WhZFlN zPTpdUfOeC>ak+E^uZ2_VB0}8ui%SmFiEbTpQ9P&-Dy@xXhQ@lU{M5*v(o0!O|1R6U zesPpuK7#yzDZt0I&-N#8T`k#wYieKb#xJ0kWgX`reVh=Tf$tMD||sUcG`S7rSye`wjNuXEnO;|$Q#M(1Kz;> z7J^%yMZbM;3CDselN_U-^LpTf*d3b>6H6#P&!3jk7W9;bx-K8#df`8RaG}_7JuLs* zK6CEK>9({p+P)SzRS7gLiqc!p?8#6CfH4?NOv`bW%QcWV^HYo@Gr)}r>vrz#GnbiW zUmW@L&$a?uaD!97yZPD0hBQC*VYmIDTXYV@#BPG9ltU8FCcgoI^pM(T9h0v*Lf(Ay za;awR1wf?kmV66c-aYMWs4Omz6X9Kp;tW@Z8BXoT0!MCDQ~C(ky_4SA#|P;EC`%zf zHBd%tDwTwOtEU90+bDDHOU`zmQBMr`M$j$P9JBM)I@z%7_H^SCDD3#YTP|y-A3b00P8C_*-Gl|nz?W1>mT3?xwKabF z+ips1w@`0%UXb5=SP2j>GP%+{+=!%2=#9uYR?@oiK4H#r5ak^#TjU7=!v) z(h@#P7_a=4c}e>48)o3ZnXaYTBMj;S4L`fnaE+Ew6uE46j$OYPF%53_R`1=~H_MZ6 zZZ?Ld1^j_wp?}#DzrBH(_GP;QIX&J#CxelscX;+y$a$&FzyNnIQv(+Mr z6HIY+zz_#1RSAxk(^FLHXe4$3rtn6{>|z8LtO6twH^yDLPU-#h&p|E{J_O$<{qZ!z z?AY(Q47qlai6OuM9Yp6P3ttrP{`2oZ`#_i!NFXej;cg>4sTra5ly#hwQQgh)c&W6V zGAnze<_4=bJ`g(g2XTB(jdW$lF7QpkK#C=8oGj6Xtse)TOxEN#f34p5NAh-%&6a>R z_Sn47Hfn%WgS(Ic?2Vl#x_D| ze`B=UK5F*&?CJ2~!qYcp`fTKw%kH0(kp%1juLE<}CX@Y$r@?o&cU%ywHoo{xDS-`4^RTU%E;Wg5;7j0cUCVAN!m;x`5z$$~u!WA8{O*fR z_c2KeKCaRsX*Y^arH+cyfXvVAPaoAuJ&!_OCMJ8GlUwU+uOBbJT@7#Yi9fXd&(>~z z&GU`dSAJ6Ou4!QA#DrVVAYbi_?XrZWpwjA6$^YxuUFZJ3^UMEH@UKB1ng8|4e;T0V zlWUK+YFE@7sB0^dttFuBYjU0Gasm(U{9rmHvH}b`pgJfX0PZC&e{~A}Vy1j#Ozmu= zm4gBk+-Tc{c7I@XPGN$|ojiL}-9ySx3JY=VqXWBwHHX%-ot~2N=U3$;*!d!Lt6s)b zw+=n~L#SKSV6D9qgXCotBh)l|zOXoW#5Qkz268>$RDty+t(xlI`EGDi@CQ5Av-;)O z^%=eoYa($L(~32W3&0hLb@g?-3smUkJ*}3$O7|Vd-pjx#bxd0EeQDA5Ai*1F&&EGK zo@8&m?(yDR+x)R)^L4(DL_B?7`nRxZE&C|)B+-#iVbeR62QXoC7{djcaNRonn{}%` z6D8t-LEUZ&tT(xzwN*%5`y^Ttdv`)z)18f6;8E^)iF8sCl)>4l%Y!TB(s#hyBY-*! zSDi*9PEw*h)@+ANeF6EaM}PmJa3ZcTw$oCT^A6vNzruy%%E@uboQJeO@DH8=aJEGNqJi*9d7}*k$*o)W*EVjodT-Xg^a*w^#>~h5 zsm$P<&7&>xM{Ss66LHM)T{p|r?D<)D} zdGU~GpCn8Aqc0kI2CKY6&G4bAks*!mpQvB54kP z&r#40a~+vz0!FMXN4~*(o}cE>N~OQON3Zr-&#OEovACOQjs!-R(BM>!cWs;Hd*pnf zpP)>vCxk25pf&=bi78fgQR?W(Oaq>JLUq=GsYqRH8|`}p9Pr%XY}$M*()pW^rH=DX z>fHGGv#vYX><j-XA_8Uzj)(HtbLuT>eBh2{Q$X0ed|2dH`+oq z1#hPyRKtZ?@W@NE8WPq0J}@!^eyompD?3AMaV$^O(Ys@3cb?NmBivOxTeXpUa>498 z1iEzVx8Y_w2Z)^gq~pD0-D&T)Jizb4*A-`bpl9HSL+f)tpvKnk>T8(uqm z7#lymyndnf`Kw337 zmH&uzrkSQ>&tzkfOK$xW3!k1c@*DOlWN}wcH#j&z+~|gm463?K!NbtVOIT`RiZ*|DO+ldsE7@$v~6zGMRzq!Oe}vVBDO2)C4iZ zzEl&$<7%hY_Ny83;_SilvA_?MC=)Usp`edhYj!^Llyx4P#z=xS;~R<{6}QG!;0b!U`;S-=Yy9X%bn>b`vG8v)p6ekcj~tHxQBB!CUFxajqO@;Y*h37y6i#3iFx zBWGFX%|w9^s#o`62l&z;wT@MMO5$YqmaT@(#$->?+=P5(i+Y18z4)bxHYR8WZf`@* zBjrH2tU^_6U4AL$zShb{knnWM^8qx|-4P*4~Jqh{6zvH_| zmkU(|s@Uj2x4{!8Nz)r;JP6niKItIj(-CzmDQvCFeNG0yPc|537HguXyf};{aoq{7 zxJ3UQqc_*m06G1gbGgmCP_{+jvg#h@6W#l0v20E22R#IdlZ*Uyx4}t`HgsQbFLsQJ zh`?U)r1heR-dVc*tLdkIaUL;HSgn8hJ1t8%vRgQVHQ=sA?rUJrzH&vs2DpO0$<5~4 zdmm3uo?PFb{N9NiA!?rc0&pCCafQn_wL0a4Iy!A3o-pm+9wfG=M#0HYQ5|kk`K^W+FuI7wL&HEdj!$(* z0s+-NU^o~VJwGlVxW31OaVg5StEU{#E}Gow;#(k=932RBR&T-ElHYv&VgcME-p3{0 zAFiEz{-2UN@AxdVN7D?ZDaV-l{==iO%v~ow*}Xi4UCv!DJMnMsKfgDgIpVyIZ!p=_ z#w-mt$nfkbH3P8J&*dr$CiIkC=n(KG_z9n7Jrg!3i)*3wH&7Qk&pEM|mp)&FMI&I7 zTNBV@y9n$FJ#kG#S&%7}i`JWSZu_FSvYX#*j+`@xM&AVWwcQ*F6Cn6QmCSOZ&-wiO zJE!g}-nrN-O1C8}V1;h0KW&b0b~g<~~=qpe~EQ6e*W-y2c^|-^NVQ)^Fs^BG@HO@|#=tjkTWp0Ol0pS$c9g z`Av0j@xD0elG=HlVRJ7^rukiZBqyoY+yc44?Gb2jsf}E__I@cmuuYOR5>t{E|9|uK z_F+w(>E7sh_kPc%v^W?*43NT*2nB({3b1-IMcD%(p?nMi6{MulWF;xcigc3Ml~lI8 z`z?k5NgxnlC7F#|5$uc_O%Q0>3{Lq-Lb5UrqLL7|Zfmk^r*^A}RZ|hp_$=*7Mx=@BV#2C+Zzgo$+^fS2*#&UIu9RpgUU283kMAvT+#V9qQIS{8(Tg z0hP_A)fqzMSYX@#{oUkpkmC$xt=jY088!<)TrHGzpE)249%!LNux#bR21?a(hhD!AJYqwur z>QMM;Y;Z!o>CDq3auBvo&~%Y5pkH-$sJSc+;!D2M>L68UwmHP%Jt@A@P-AR`y}`FG z$Exr=Wk|Tl=^<5spl2`)WyH0p%!L8ChB27#)6a9D6!bQ)1bxaslgSTZ{l;OltVNfR zfTj08xzqytFJz=7>X*y-#tTyH4W{ZbHnH)%Z{tD4A5Xsg%j34Ki%pw9p4_6LbQuS! zHQ-Rzl>G5;5g2#++V}7KsAg&(nmqN1Jr7QV^sxRqJ^ssD7ExmDwG5-j>&z+Wv0wQF zGU}TB#>gnypKl73q3=3O&BI$f1>|fl#PBXY5AQ z3L04xrRa2+xq7aS6$OIqVX81%Qi06HLUoN|REyxos{QmeW>B4eLYmfQrj{;tDbwh~ zP&w{%qeZUC&|x@H;=@ptmA@h^T63sRxeu@;KKD|J%>*pgk#3QCUtZ;Dx_7|CipYv5 zQm;+MrM&N^aJOD;J&Ndj0rdGlUQ9Utblbnb-MO(afCPW5#k!ZpUCRMFfFg5AB-b!I zeNDkbl2S_1<7{$`$~BDm_JZ&?JD|>cDG|IgjLAzOs?WY_qGy6)v=k$ynv1%&|IKRi zv&#)a!;0LK(I%)_#IE5^%}RzK!+(fL8wJwDz1OQ{tMIdh<_uqa%!IZT+NB`=xjDKg!{K!bA=E?e8n@Y%GIv zaNE7Mt#3|lxUM`uftGta+VI6o0`B+>z1L2BYA7+JWnI-CGELMLEy8#7qe(w3#q5Q7 zD4|a)(z&qy3blwx)@QJLLVrTVODXxgY79`s8fBFN!j|OA4Ki0*uWpgBH!x9E)t)nH zMR=YdEX-_u_382UB(cvJj%lzZS{Z4SQweT5*polzV-6X=J$!6>XR?I&mZq*OlFX1i;@bw ztQ*-+ynmYz+DGg4KsOdp57b66}}1#ybbSHoVNH6`QArq3VR;$n)A_bx6|76ttLvk$nZhv zcf(n=Fddc+gKo^-G2A-WQap-`x#|AjS8N*Sg>YY*-tNq0=P1H^s!zu{t5x7U>eED~ z#L!GYgu#yTi>63W8Sczoz{Y`-y27$#VH)nsuiqoBQ{I4Cdk!}`w!4G|J7IUYHMCsx z%np|kl8F}&@ym_SPd5~_w% z9Bf4k*za=Au2MjF^6R#-=3X=NU@ET{6^rua(y-z~V0wY7?h}HrDB6RaNj6~bW-b)v zL_dB6LbCq~Sah~;escvlEIqLBO#8e+v2jMC4c1+r-G=?>p|E{i$nuvxKD7u$e+^Yv%uo7NPu zPw@1&)S8P0PdfC5a2Mh)9z=2zh#_d~g|6)w?o@TrP^f#h! zdMJHL2H{41+x3rsBNC`*ImL@^`cy7EcZ~zUnB-E!QU{oL{2{XlHra`|sH)SkCWb>j zYPYi`3SJDD-1EV6C>6OyXE3rW+qR&l23#Eqk6ax|O{uFr=PtzO@0a;d9}8lZDCyr3yo$W~R1?|Ty$D~uHXIW`&hx%GCzOF+|eL~Lce+`P}OLZaX-+Ur+O4Fd9mS>_2h3oEU-*TMvH9yKko3a`RB z3!i1vhzNk3Hx2h==QR8CIW1zK%*T%sG&3)R3I4ZVQ~UG7hTL&WrvDrfC-KwjZu1OO zc4Kv5#h0vZm7x0p`1|-nADQZW=CIk_8-?>J1cCya_BLc3(sOUqZ8*CzvdEz8m%bb8 z5=O_mI7*#woCRhb&g;GPr67OA*WGD2z7@QexVi9f6Xcs-`55=syhF)b5XMh&09+k^ zQ!%483w;;{p^dK{i;QSUx^vx;{Z1+v&C*@Fi%@Z~>KfBHY==X?8rqg*r7ThUqu1pC z(CVJy=J!BdJ0+-YM$Bvk&`At!ErWNOF(TDgKMYOzdXBg>|ZNXkV2bvIA?7j>(lCldQ}I&G+BqmB2`!jkfISg{Y(#! zc{o@h&yO@o`&_9>3pYpJfZ21}RzRB)q6TX7GuxI*=9Z_8?}8w!d#6Gez#(bu z;2(AZT3)|8-3F2mu-5dfz63`@L^ALr))*=Ejl9;eO4eiRp2~i5U>+}L%zpIK4}+EZ zQOlE$afr6Y@dAcXgMpL`44t_e+S(9jNZac4p1zLJl8NL<1j|5pQ~U1QtqWx{T*Dg% zQR7(UgZ3|ZZhQ~OTV4l`*he3mu&=`Ofz)uyC?9*ojW=N8XG$@6gx5l9*f_G$h4IeNcB);($ z6%}Y*B7-p%C6L&EEEt*Qq$|`(AtGsw0w|&P?#9sw9RYf#po;^Em3Y=f#+t>SEMBts zX<5KVw2{6@J6)l6S@JXE8LUR47i!ZZLEsgpA3VqS^m&QhX1R>H+Ao0thxRNO+YHfy zRdgH5IuA!Q;gwgOE<|)btGn{@tF7;53Aej8WlxR?%Bzge@vaKN`7DSD(&rP%!Rph4?}##yh1>M> zy0o>SFj`yEoyCn`)7S{!z?5$by1=~nzh0h(x`v}9$pt5~k;emAp^U@#f0qyQGCPPcDU;o!R=wA4(SaydV@% z)2M9=SSw~(%I_K_lrzZ+qkvMdEj4`R{!#$69>|T<6oN)7K`+Iav{eE+n*9;RvGQ ztQN_;(^AB; zNp&pYTx^(BOOAj5d2*i4C~f2!7O99N!$xe+(ZVAtn-DRN8H_Fpk`Lz5pIyMMml(pw zzlEc6^!A4Mm>B>1C5WxxF;x&!Zar_?`ti=o8&?7g5gVSX{q4RBpa6?OE|V(vnC@e# z?0iMB0WiGqUDWqf06Y{7onr*(rtMlJbRt$LQJ$UUp@y4&dqFbZps3mels-W2XR8qd zDU#S7cs=c#yN<-|v_wg9!W=m;(uB3D#S;ME@?|zDu=GjaYc#H*B*VrrrUjtD$f()6 zD9nEyaCU+LzM(fXVP^U4q`=@{|5!tBVGop5rsAp|e_mY5#118Z^*JRfsA*Bi+Q05_ zUtYR;<)5D4?H;|d2}}m=cLHmYm;b@N3hl@lQHTPjWjn--9}1YVsSXMk)v2LGJ(eyO z+z$K^hobcN(r_E0t7m37?ZbBgAI~hF;6w~+o5(pKH;7cOi@6$f0Gg1E)$DBoc%=W+KdE_#WKs{M-sMh>qB_@i-s8>~p$BFo zC>XmyQ(~kNc6#g&*>%C%%V@-jm;bT0x!HF7r=y)f)v@uY`u03l?Mr2KahyltnQ)1s z{mW8cX;Q>m_T+=_u+~`6KF#%D-KmajmWDQ=!9Qcpl~_@v5NFTD2H++s|LzZVnh&p^nLJyxI@Edi|qBea^ zM0euc^I*Zt8+W!`m7Pz67uQXw;oDOhbMG=RF!0EeiWeoZ5+-nMm)%F<6hf~BK$=R@N^@ijZ6t8;#X&h6jr_I#jvESIeKg*@&xMZA_e+J`{yd5GF&9V$@1}~pNYXZc0KtGr@T25dAQZNQe1fmQirw-fz zftcXG7-IzndK>9(6*db2aodvJYbkf5DR4iVr0)lM#$0LyY<}$+eeKdd9e^9*Jwvlg zlzZmW9zJ-0n}2-Rd3@t{} z>honAWkF`~Ir41Ssrqs+8QdYf*K?Z6`->mYZ_tZ{4felqi#(rp-S5ch&z2|^NjpgK z?;P6q2hml5{h)JK9-2_=q;7uo*PZ&}B&g6AA__K64I80r1YoTsLIJbT%&APhv`j%=V}>>O^nrs_A{PFP1rqGRi0YfW+tZNEcWH@1 zo6qsK`r!|0b>!u;Io@iW1kc^Vx<40`^X z35qybER|SC(d!h5E(9Uv#61Roe_*EGilz7Z-huAkP0e!JJ$8xjFfUi1 zKHf2OkL&z4NZYb7&Gwlcg=;!fOD*V6aI0>=t}ils>yy$u5ocGn)1h&4J>> z^g6><_n@dyNG^Dp0v6N1@kGCQckB6gcU~@CtOxN-h6kZr_8YYZ&(q6xLQoWcT0uDc^g+H)ze1P7N&FhuV|7SI}?%m00~cbzz#AWfg3cvhh- zE?O?*)9>UT!_*CB>vKu0A{ z0@C%|Q2!#?wbF6Xhcs66hT7sFnKqek7NgWx^YO|;8I~?Ez@F=t{MlGQc9XMJ{zHas zpjDL-d;2}Bbrr{--`%>?xiPy%v~2}u$I^o{uAu>AtQY*0m)aW7QoS+b0e9cPePa7N z;PTbaZcEY&X!{E7)>Xc1S3E*ztAJ|i`X!RAB20qknb3MbosefY<zPq)NxOL;zLapHS>-^lM{$5=LLh9^fLf!$<_pAY025dGF zWcrzoX%B&{{LE%i=&IEAgSP|5?|u$1;L!P-CxAV*YXCs;64$VNn>g3Tf&nFw;}^vO zQSp$G0$2zh#l}1*WVZpDyNi=#0<-PYi_HMBHR98YI~l;-2!qy4ps2O+4BGTHv65+r z;1nFdi>(Im2zmS-t|}!<60JQI9<0?|z-F^gociu-RK+e-0kO_EP}u@XWY)PAt_)Lb z;Na6&y=}zSWuQOq{BgGP$A+t1!((jR&lHsGf6krVnDM0eld3 zdU*>7E*a46dUOP>>B-cJs2H!d=%~nSHZQ(b?j&F=#bWQ+nBzzncIgt)dQ3q)*-z{ z1nXlDZwt84^ z+B6tRII4Eg*d<^~FL8#lqrDX6j*3R1WQX+FuX<6S$MO_LI1lFrU4&akD$cCXu)~8R zCpY(iqqphDqn$6$cD^Xsc&A};9HhK3rzeGYI(w>6!e@6?#QU*+i*Mcib@W6gWE%jgL0pKl`LaxO#)4{HHVTbu zqp}Sq((*-S2H+!BgvBbMy1Cv|T3yK$#g)efM6}#0d~g(6a9j9rga6p61mI>l!m!Ho z@AsPblGG@qJxAFwG|HHXg!A9uPb34F1-7ApGPvMdN9D%2W50@que!oxYx)^Hop$zt z8CtX3nx}@@Q=z@(4x!9#-F#Z`5?nDqp8V-<_tx*h`aPG818)Yv8>GJnh3k>SOe@j5 zzK*H{xMLWMEO>@xfCs?r(y1i2&jH}?Wv9iLDfaCJVoAJ;C$E=4583tl|i0gH1h$RN%Kp(&G?Sw;EBm&SOXk&1sZlh(Wr{6DtR*Zi9jHs%0Ntwp@05 zWPm)$0gOV&u7>{4T@;!RIo2K<0xh4*iy`CgUW(+s5$U_zS7jN*`p%@W1eJnP)u=mn z3D8NA94FEKll;|dT;BQOJTs@T;ed`iXH~>U2j))b?rCmYDY>P zpvs0)*r-C`Z^o~cXzaTs$yp!{#iz?A$k*)R3?TMyZ8TvV0B7N9SA$?mklMBH4I|;# zrY8=KZaC}>wK70g;L({LMKwGQ5cI7RaJlvzhNQ>No8T(ArE+An{VkgUE$!DLihwCR zSs`M~>;qH`D>(+L*$Q=TPF;ENedm2}gGvuel#l;N*gqR+|8j&UK2ar}=-K}G35F;{ zNm)aHhn8y11$w+TK!4m12P!c0Bm=gAlO-RtX6i|70Kp=id90D0AP(G=!KT%z{)@#0 zAB}Xp8JQxZD09~`*d?S@Ig?z2Q&}VZ7GUb80z=V$wMEgvybVv!B>ce05+KAo^?Z=> zBW;bDV#Y4zu4zuY${aUPo;YJfl$2Ow-Cij|;++X@)Rg~b!~V$WiUsSCa^ulL!Rnn2 zTjz_;O&0(hPimI%%SuT00atvQ`uQkuf;DIK@5ZEy2lxNo65IeHNtH*Icw+{lpkKx3(9j95x0t8 z%uNdZi|-wEHsotQV+IWPUW~P89=p`TKA(!F+kSxpG7D=@76>Kr(E zY+VWwSX-}DJBn95h^>mK%aq_GLEyLJApMOHtO6Kya9WA3Uv_!VNL%|L*>;2w6uf+Q zYi;YNk2-^iTNhpdx1&WeqYDGnIp98kaX7#PApyUb$ItL`xbdg0AG8!sOo z|LMER1TU2~;xVl(Y&j#Z+VlGMiN+ADHFyJV&?0#m{V;Et+~8|J%wWBDo|~W`uP8WE zN&(@v=UGS2QF1El3>=zs`m>NwybsvWLGfwvu>i51mZs;dzoxxdD$&s5Jihnmqa_pe z;X{NTatycbr-Pz@9&24JvFD{Q6=5Xq@IA&fBSHPU%b&f*&~=`s6pi$gf+TPZ)|p`h ztzHK=0;*DWUCK5-el(aWMz77>&wo27KBsMAY|1!HLw~<7Y#Gb29U9?id9L%ESgD?) zzyBcH@p$#*lWveg*}3s>Qwbj7N3ZJ<-*UB8>>AX%0Gm4qKjr(D0SRCtlp+?SLKSHl zy|KYTTHU?x^yn*GP6HSeEh$EjY5_)%5V$f3ghk4!npB!)owh zY8?;Y1gW-#y5Ptt1TZ-t_+6164s8v)$O6Jpmsbct{*WBd{O0BL21*k3upP<*`7_e= zItA7rre3myy9ix{?;X%`3*{CZ2=?`~wdQUmW3S@SzE~nNhxFLZ0c~)n;>u6KI~xUC zF95IPzKv+d>_#rZ6<*d>G{gt0rz|eDd7k1L7B0Xn!U2zG<_77yScxmIE=Rb3lDbAI za#)4E0GWIuNz}x7xgAyy*1oSp~mdTiZU`&G29(=gBjg}F$N|yKp^L9X7?ZDZFoi$~Oim<#B zW+4+0OJKdrO-5i%`uURJOlDw(_#9spHZ-QpoRVshbxB^1)lF}@JPlsaAJ5jR(4p{vVxJ`wKQU0lY8qjuS5ORXbo43zU+nj*GbVCSg7OL%#f?q$Q{tI;M7oWV>U>qi&a$&K_S&tvN zDEbNzB>E0RhRtp(1{yt{f&$vZ)WjCC=;^>HUuT9tz#LWl%Jg39JLfaFfYW6oFFfS~ zbw6m!>X6$r-P+l)N#WHHul{x%poYbpQ?jo54|Rbv@DhJ#$gPa}|WLsFd~Z6O+3^Bq=+bMO9g ze>F8rS`SZVizg@{`@z~!>NSeLG|K-&RU`4a=9PY%K~EVnF7MVu$$(NfMF}&PHlP?b zxb2K?mM8(d z1iTESlyZl%A|=XYP847Z#UhuduTi|@d3iHPhP=*HZGSZhMd1wVz_NO|Dji7Zy23zx zeS#x(9%bzx9JMGzJea<9oi(Se2n+sUtUT)6Kcbb~87+EZC1os*I|I6Sac-T;t`TCI za2Fde0vs$y6`7|=&iPU}#`@voa=CIiPcacaqv>C4jz2Y0A>%HvP#+?k0_y%4*h=@H z^Cc))R@>&IuTFvudKYZec3|>cZkk z<@V{ZRv;{7rP;7^k3U`lhAF?Vh@jS~l9oQBkU+`=p-Z=s12?vt2b=mpnKC<&-WK|k z{nZ=#_|d=c4C+?kD3K<}DfM)fs9O`l%>XqP7nAD?iR3y%24WE8)=}4XB2-q|IVI5Z ziZv_f`iMKDZqBh=B0#CAio|euFKG)?*8Yqh`}UWHG+^uWI}3k)Z(@H}ERfz<3pU2V zLmLhG@7wOYvI=@0y1PasU&TR`rC+%VN;s=BDp$P^sG=FXUG&zQ0 zs_&_eiYX(3{8 z^8#A+SQsO2m9<1WK7z!!{wA7H%%F;{u56tDN=9+!>-FoEnJrdt{*w3;~tcQz| zg-=Jfet5MdzZ-qUxVYQoOWgdmq};$L95H?rQC9<3m8VHPXuraR@Kad5lp>Y5SenKw z&6wy8%-B~H+a@?*ik;4wCz@k|H)>V8qKK1ZtHSl@Jp&Xth|%z2Al)RB0ynIdVtL09 zHmdFC&Ar=5sM}z%CUuD^$@O2rK!P0WElkzm=r2B0o+Ix_Eg_C&~%DWBSnJVV`M>43TZu~d;0ShH%R+3_Q zm~Cwi(tW>w<2`VrW+sMG5JS?K?hQ!tgL-tvMEWE@-Ak1XAPYH@W*#Ehk*<#pUUEH7 z+$6FGXh3Vpd zWeJEeseU66FBTi5eLGh#FH@A<1Z{#L+ZrCVk21wBFF|ZfwUV!&*>t!bh z|Jd4myPwj3xlFPI&s@3>Q~7Rp!WfxXJHvht4xiGl$P#)psk&=_zo1L+L7AvHBL>#Y zqT(9~Sm^K`%@{oii5mM}J-R;}$SYivC5vuZGfO{;cc9jBwqcp*9iRpNMQxTrk!D^F z>wj4umzP7+=tgH+YFp3c-^U`Qu{os@e=}1R*Gp<(MlD#F*NGMC8j3&bRg0Dk_%z6@ zi4ehEmJBL><1}N&2y2&#=JdaCRheE&<@%rh-k1?S1PE>xKDqyC;^xNV?mNIA|8K9} zM~3e2-)z!vp$%2J!ST8q%v2Sv)o{h z2*B_pesqG9cJDB?Ev?FjflyZY<94hUven)`z44+casNO4R1o~J?8-}jf9Gb~Zm)tz z?i*-v$XvpBKa9tS>rs<^wIqxy>X)vInP@@;n=g9mbKi0puhEUCWT{ztZCA_K2xIWZ zKtt>j(r{$z)CGzkP9|Wu-mL61FQJ@M6mEQaT{A+K=t1rwDX0k@83Q~YUkv1EKGHMT z;DbxU)k%w%{Ct0VhDarHKtcM{ua|4~R6>=(%W62INVPNmoO0L}S0g?LN%h{^ayzR! zUW`uJMPnmgvJr!3fsYs^c_Bs8C89blz?}W{zU*U)MgHTTqgOV*1K{9~{Y_VVt7o@z zwl>$Uu>Idbw&AmxVD#m1Vy7sf6hGBEJP!QcZweUjthzyE>>lr8Oa@L-Bz=MRK;XHy zVg8n|e>#ut`@}q4^(ofiYdH;shs`V+TSMdM?!24EMpa3(p=|4|!XrJ{S^Q{FLz_*{ z-$*596v88jJ>LapJYYVRl@xlb5*R0SF|1+*2z3_w;9iC``wr6@17fmn$+%xoRyuY| za4*nB(pYo)N9jV*t|C95)-T{S9W=1{;*_9Gm=JJubn@1&lyKx1k8+XJL zBVg(~k2Z?SuyG42F>+PWp6f*I*m_5l807D`oYXtXOgvKhdPG65Idfd=CDUh#uZ+-; zG)=0D9Qk;elqpeYTn`o6HX`}jK#J8$E|v*YeTq}ARZQEg(wXSx?azy^UKMa1j%rRzChFPl0(~Nz@9kQR$a|IjpL909>iJr4{@o03r$@ z+)3sEoO+ShD0Via0bcfP-DvS#nH)6mG;n zXJWL->=OAB6;@_ftbROq>tFwKL1Q4ua+tRt-Jef<&ULL4WIc#rLTH1~B<_44RjpjA z_gG^Xz1xspDDfJQy;8=PK~Yns;KHjT4Zpc?p@e8z#;xQLZ-OjYh#E4c#$DN!+5-xO zR*iIShQK@JT)Ws5_-?hpC8KZ=RqV{b_~la;MU|@i&lE1KPb$&^PqQ{cANWlA>HlRt zgJ*&l>xe$W-*OGaJd3o|vAPCkU`CsW8!Z{;;v>@6bJ|GeJOr=V{AEeT)`z(%RNPIO z2(2X30&rzdzQ8B4A7#K&;Bru;B1xQ?1$K3VoN4w8j`)*jZ^fmVPee9^gLpNETDrGQ zy4-LCBsR15EAsdK#=PT|?2U-=C-Z>P!<_6OgT1uyUgYq*BC-C_+7;K?%B?+3SDtt; zHg$$VXG2H2rT$IyDGaVf2QR)sXfWXCi4`m@2eqbNYp9U^0czALmVebanazw3q zHrp60@t)6QcVSW;F|zf(uE9}tJy0RfnEc`vA*jatS$5;C$YlJe)G4>PYN(a5()hu% zBpA5q`M>>#*IgIzRWd2~%FJJ)@Yu_c#=>9R+Pljv+G8V&on(+SOfEFtlI7dIWXM|- ztA{GzDG9%z8Lb851NW_8XgrwAm4a}>7}k?p0N(82f1~=DeNQldqgumal?js8&p#N(_e2Ghz4BioG{k$Mz_L5jr+~{a8{|zH1 z7#TVu;q*9Jn5eFAo0v1gZD7=b)tvnaoONJn_X}PwW>ZHF^m8-P)uV!^12>SU-7qYz zCrcibEc2q{-cy@ay)NGT{Dh?}^MTvjEn6 zSal{cSO0;GcJ9=SC>VMS3r`?7S6t^c#qlfbV1-!XE$Jc4qzyz(=bnuBi8RZoImZ z{`#0U13!+Q>~!!vmtEz;)*St|8Ng2kd1&`~mDnXC&qBVmRABa1iZo7t)~=p?SC2Z5*5H}`Oj7ZF-vvwV zq*PCgcuVZ$>H2TU4sUcZ)p;#kPljx}Kq}A-+|P({WKQ}fNX3Ud7Qtqfuk-C$wdFDh zJ<*R3xQu-+wFh@U#azVKWa1_(u&`j>3-2`q#p3Oq;ZgII076N<$#3a zO0fyYctB2hDs)LRY92<&&};{&&?4r;Y5KtijCUlhttT~*^cDk7B%T4{;cBjH<3k_Z zM?x067$8sg-!Np6e#a-`M-j;duO@!pKf|l`OnRky)}GBC6XKZwd*-R=Z}o!bZCCdw z#H7hIzkTJ_YePtvz1cvxcaBcqI5hX7|E~ug3J$ziw=B4pL`96#;MU3UI6kRrKC)z8 zhyh34KwPhgY!$lvGdm_A?Q+eWmX;JQu+mraC+$w*YB)p3a^$;25Vsw=ypkg!}*j-fucrb0V&trmYfO6ml3 z%l#Re(F&NW3Qg{sp4KN(7a#ny2Nli-dS}7{9RCLfEi&KP11TqqW9XN0<1xyJqx0z z2_Ur)!CCQUrCNYFH65dO;%T#ht^y3SC?F*~lV~QM1**@~pH_S2(X_1-7T-<(QQlAk z?!8Gd7a#VyDP_$4_$^{a8h7lAVvx`Ru_bzvRL43UqgQIZ^SV=)peG+6E6FeSqGOwD zcL=cY{`q3lPbXhJtCPnM0|jA%lP?$<{fklB@J`qA0HWn!JC6Ola^bW)-<3^+k+&0N zM3G6VtMUS^Y=Jw23UDRRK=> zy&O3<_%Lt`@x=s9%=p(BFt^wIzOtor_b2IAOT};0v;17NbQ>*kojh*7ak>v`GcIRZ z0sAj{vPe(SL&Lxw{heINd9CPa7qtNJYjV%O0nR5l)5Hs2UWu{BoRF>`4}mm;7e9W# zmKA#PUDLmJzP!Klafus%WXlRyCVhlRJ(evR_h>A|V`46T6x=xrc#U-@4Y*zgKHy~t zmsc}f1|XNFthfvUXe@+B67cLTp+ql@7k9P9Aicj4O!1J?o&a3t$;j%XfI;Le zRgtO(|5YRHv+Uv>DUy+DdMU17yUJGKl*&;hOtS%oZs$vH$4^f#KK^SP^?g>s*4HuB z#3Ao>Ve1g)iFVe&K-P|m)1yc$H!mx>st3Q+FaPk(VHi1W%*&GCxM+-8H1h2iCJ&K` zXC{q5&9=QNbncud>_0>b#}dvc8Fa;bzA_r&+(?0o7H{bQ)xAdL&YeX;Zpj>oCYLwo zO_iXgB89PDpUqwqUK2#T#b!9Lh^fyix;%Gb;N;hE6Tu-?uR95@;3uEK>WwZhwSJ~Y z6l>iX%pLlj<-F*K9**WZfCP}96S#b!IY)Hu=1o}k!|2JaN8YB*=YK6I)}HrY+2Lu9 zckYp-&rr3aqTGd;VwD(FW{^hxP2g08D%q_Nm zv&?%*vrB~DWv_YgsNKEMOpr&1`G-IVw#xO{tIhWx-;aWw*uS-QwcrNzI%T!?h!>2d$cJ*F7llDIeok#Wqlfyp(%T!KXefHh5E!@5)`uO&1RBf`wmnyv;3)0yQ*y z&g-_|`7WwIlPzD_VGR5A4S041b5WHgw`B?ePb%e-gwp0ptO6MT$zwV*nXonr#G3ApR1(s=sF1R(9r#IgW3H9{owOCt9zrTy3?UvNC})JGuWt|BJrYoDrw6g&ZFN$sQ{(TTl!|zq0@_B&~Kj7 zoTqR#xuV_az8T5~i?RcOt&JZag8HHkl!05J@7KVylqS>GcS`zTj%Vc; zhS!G1De1!XxAX^YPg-$O(YdAKG++#dBP)yUE^t&_xZpf=#AA1gk6n_kTl@evIje0r z4esK{AL<*rNIf<*Ul><&q@s^1i7zfaqJ#hyAsmnz#eJj&PVEN@(O%K%zkj354pruq zj!ou9dg&m?VNoLtH{OK?9|DureV;6$jv7f~_MA*)nAThGuO-cwh9cq&j|$DVC^x zYnFb!*erIc&w?ZEmZSSEcF;rt0iL|PNoI|XtPjwA&3$iyJmpNlC?sJ0EIF$BwzXvF zEaoLk%7B+G3WFIpncAlHrdEqv6;ktJNmvWs023p^MSt3?f_p?QR%FkT>w78s6*@w4kvp?-O#%X%XCDUC3+@jR|ur zP`YjN#npnBPYdS$_R9UJJFmX-dLI~ce0i%hCcag*Eva9f%C*w&sqG_eBi_?nC-;59 z{_>N`2Z1jQFR<#@$5-FX`MOGdZ|^(f+c{m#lp#~hKkxf**#Fq~cdUO6``ngHYN+1x zKW`UoJ^aJ7;PK0GqfuXmjZGJhy2^5%x-C0MyCo0|T;2(c5+92ws=-h%!jA#xoenHj zj5jbq5vL!IW{j9mM3dAA_(y|^UK*X|D(mWTFOpd*t`A^L4S09%1bA?BQB{k;%nsy} zm9!5u^k#55iqKx$c0gq{QpS5a?69*+JKBd@A00ZfLHT0+W9OVKsmF|?q&V-fNK#y7Rr^=e+YiE3q|WP-7raQy?-LuN!rD zcuL~b3tBI)H6~t)(E)^s31ADIG&D|J^VA?J5>cWEXUvL5ZJTI=CJ-9NobiGPo6|{b zS`dz&B(U2~n@NMyw6sRwuQTtn-hcW>YuDPF{kwd>-_I>!>InRQZeiAfU@%anF^tRd zHBHe>wZbr-5%dt>RzyssSP(*Y$HSA;Vri2!bGRgBQ~|@|paT#}hEbr7T<_%~qwg$L zYOD(_MU}jpGqy^YL$yH(!N7!$tW1-wm{Gf{iWh zpKo|+>+i1&^*@>-AkK;u)2wwD@lbCRMRsL)uuiF4RQn-`MRhU{W&;unhV^kovwAzW2e zy2@NXjqp&*OT{xMhm%C2X&${fLf!^LpHy?LTXf1OF}NE#q;?r8JXWWgsX@~i(X^B&nVYw zKOSzp=1R2br=&=k`Ny&<=m9b+?Nn!6FKqAmNVGI?WUu8+Ydj!LN8tG_|(o zKyzLxHjxC^x5}oLVPLKxs?&Ij0(o}gk;Wl=3QScEZkSRBt`g&=NrVyv@2BNSD-Sfo z@Q&Ufe)g8n#uoAAu8J8?Rv|9LQW^W`$*`T9WDFNXI8}T+6c_a9 zGkRe<`>%JRrA5aJ9u|vVFe$oP=n)9*c(JkV=^LF66C0}048EqUgZgUx`h6ll#_M38 zcgGEmW9p0A3?@_EeS;_U9(4+&Tq#|Qo@XiDNF!sq*T?ud3>f|Mz1uGlTpnKoUE%F^ z7C&#KZIG+kg^cJH1D`u~lKa?h(=NJFEOWV>8S7Q^dJNoHrAB(d9Qw1hn;@URwm)=X z-oZ^NSe2d4D3rI|ifo#xkr_YcXRSFaHK7H(D^#Ep2xg0Jn30*G%nF{1FFRT^CslESmp(-lk#7bb(kUuos!5tpGpiJtfxxppPJvv zvY$|IQ^T%05*IT{olL&@m;ZP_h;lUUiZ~+^95#Jw_=$Fq^Te86#tcb(o}4Y3?tTvI z3LM8Sj+&p9UYe3-*5%Yt%9a6y7eat#C4-xKJ~ zZ<8t-*LfOm;A^Kpjk;*|2uhQ}!?r-2aqUR|Ku|U2pHd_lePT^Ufrf{_yVOixhkqgK zq7Z_zd^G(ywl^#Z3WqpSYw!e+nthJy@JYo|YB@{R#pE1@L1M}~T)V6^fh^FKY9h}5mI#T1QE(W|s28%a>;tJ)%uPIc#Rhd}8(SRj9LM z^Yc5mdrOkhObT@?x{$)XNx@WmURHs|KkP>r;|?|ycpU^762Gc-m+W1KF?UyFa(1Ru zTbd-l4MWIz|EvuQ_4?QgRi!|%a-=p_asm@=L_jQ?FJC5AaK7aU$VUWJ-V{~nFsHDA z{Y<#?mq1*FTJjkH%u1OKPPXeB zG4ZZ4ro1*C4U89e25V+K?=ML@z-5vr?GWi7eUg}j!hBNh+6&fTNa*vF=JkF7(K>2d z5Nmn{S{yCu&7=xcmfz7Ge&}3=QO&?wzuN-Y8_-FG?&S~lS`^N`TCi7RDg6xa4;Zxg zmH9VE8(*MX6b6|8IFMlCde-7pdzsgUWdu^!<7ACcLZAKm4aT0)y2F743(R^>2lb_& zQ3eEX^R)ht|Nlt)eQgp7mC3iFx}|uv`Geyf9?GMWCo&o0CMlshGtG!Re9%-Z)pET8 zY%hJ?+-@M)ONrBgq%VXq#~}a)&kO8?$x~%Kw)}xrf>q0JL_o`&>?YtR0$uDfg|}|N z@rtZbFl4?qC+3k+h0(7JBw5O~$@!>NqM!2R%efUHdqO@YPk!7-PglUZIFc=ajKZd(X6MFn5&8`q{g z3BmoRR;MV|U|erAL{;U^7;JDyI3zF&VeJb)3|**0niWPS{@D5Cs6pncrpsN+8-k#v zt#+nH!%kQ$q5GIdejb)us0+p64wpkx*Hk=^9{Ll%REl6CzOkcc++8V3&O79{XDH4V z=?1)c%7vDTU3a$9xA)5G_FfriefF!?P@&|kngZhV1vI&~%I*;XGJ-BP&o|0_&uY{U zUR&>Vnf&(mS@P%(3exot@$qI6ai0P#&1`%ZwJJAt1zM+>;Wg)_Puh z+3bv@!_S>mFmutON;WDw{S1ceof_V}?H!NPr3L~mXBCjGQQN#zFO;hRua%MK#5*O7 zMG6@Q)AYj6R)%ft6J3Nd(Av6}v%1-3?$ajIp?IUO2Co^y`!Vo|B*F`P_VL!k#Y?S< z?M(xD3x9t_!s=w?gUg8V?Lk2?7e>3Wz#!GrZ)Tec)=^A5&cY>P$iuKDSX@56EimfG zZ-Jn*WXeRU1U;4nqNsu(<1ITk5dKe!c3h?1`e>tngRTw#EtJ*Zu9N~|rF{~3*XQTt zOZ%QghX2zqbFoL4CZIVHDO@m{l8K8AG)`4s0PP=8J5o`XLt129JcSB?`{YV_m{N4xbnVEE@Du!1_= zBPs3x6KQ}50w78*Pbyq?m?$LX;^#_GO=uLZy9;{2{W1#y-wpG58cH>7wr(K0*PW~3 zN#|DHIpeZPa{DI`i)qREJ~1ipn|9ItJJm=DO-w&J2cjz9|Rb5KrCJm(M}~JmRYf*vK~kG(=04JDSs{x93KhGcKt}~`CX$~ zkCl@Qpa4Ld1*7SF)jKAk0OM3nFZmgjOLs9s**utX6cFV{2Y%V<4^5;v??$pv;f0ql z)Z$Jx1Clqv>P%hNaW9xsWL?+9jYZP+8{J~T9P!n}MX|K&@gF@?4ivx2G?hUB*;b%o zvpna=e|#MkfE7k2-E-8eaIj@jmB8Wp1%Wizs4W|!;r)Z5R|8kuU(eNqcS8=&Qgzs_ zs_1};zGi48hmk<{ga!>hm`n9-W71)7~m*=ncFO<2fcG_EQg7Mv^}93cW8r(zRn z1sWkyyC$huDqIzq35w#z%r%nCINI_nTA^URN_@@1z~e^!p@Mb>9Wi3mzv)Ws#=P)Q_l9-CZo@9aPeVh{Nm>?wrv*2TYvbp2AQgTI29zW_jCId zMvB+b-gvWX2Vyb1_*sRT2f2-v8tu440**c;5ido};t$!21_0mHTXt$$VA`(S1^$YW zWX@MO%Dd=`m+QvusM&-;3+GNO6V2>{lWwDdoo8wh8xge2r8xGJ z<6F-!J^!ZlX*o=n+t+{C3YwhNmCKZFm&AD&Z|aMZ`#QCk1I+U#V5J1}qyTvrX@y}tAa(DUV(9E_4lBIUI0`Z##6(k2kriT0UdxX zCwHH`i&UYZja1Q!!H_-0vJz_~PB)#!7?Q z3j6-2JDZQUPPo(|k0{2t>s5~*Bp%RP5BlaQQ5Qp~AX7#T{l*@TN%SPa9CM8<**VRl zS#EewCR$pq!YKsLautRHrFh&(i#7_@&#sPVN73%$1G>UbD2lUc;;U@;Q@z`CQwZ1z z!;;!Rpyx`d@k|l}O*c`4N${)4j3a*Y?}71jI%DqoNT4Xf2!qn(^?r9yFUfKf-2O?* zC|0aUx_jNkis$i@Ibu;vl&?|sv=w~bfBN0lH~ZlliTYdf=CixX6L?krd_MdwB}(z8 zROlR=f~*E(HlRo%mDTN#L^QQmM0b^mIfv`)9%2j~%1NVsoQiw?Y7-x@@TG)7ZdQ7k zisEiga)qL&Y*_VON`*3fQYx6&jo2bdviabLwgM@sAU|%s*Vh8a*a8k^O1e=1tSU0n zkX0fT0Lefyqk1toac$S#{LFFysNAW|3bNH*rFTm(lNhzA{EU;wfl;QgIwm4v_}7!{ z$|#pN&J2WP1qb1#zx@2t*5CGSJy_d#IzsD%)nL8Yv^ra*A-9Nl-A{VA$hI+F^u)&=(cP(K~ecG?(Biz@n^ZCUAWA3rH5l)xglVW|dK zFv6LG-3Xlh-H0p_-iFHQ7i1a)Ql$UmDG*Sf(AX(^XKh&o_q%pmtd<=Vf{kb|H%S|% zWxGo%4Fl0r`her4@a)C8$FCVX7T<5*f6%CMwX;OlU4rRDXif&1JUS&c zL(4RpKL@tjYY@gd<6dJs_5ZF8rkIA2oyvH|K<>GmeuWaJB6vlNs}1 zmF_AdtGEKyTD!ZGsD=59etS_U^xeIxee!pBc^U=Rgc`>$U!fS6v`35wDm5~(3PHtf zZ+Dn<11a`fQ(4w-{DtHPm)&iWC`?kSAMZ{v-U)yI=_dY4&idEO@Ba7JPwk5b(iLQ3 z`0_28>%5y1^oUl6Gd^R)t8b7yw(My zN;<{`vSv(0phm;LLgD3K(fVmnBo9JyP#nveOu7depfX2>i#RZ#Qg;J`b4*7g!(9R+ zXg)`6@`Ayr_DFwFA4N}Jz}ms05|Jg_ImWUJ9WbdZaa0U9f=tB125*oMk$yDbv8A{u zP|BW^8VjWILEWRvqG^(mH?ND1R#Nf^;KL4Y6=>dsfRK;3o|g{{Qk!q@W^v5kj75ER zTyMqDthg)ntl0IwB$BKc+yiqX1Vdg}nraejc+XxA^}DaIH~@$yAr`o{&z#^l?R2G9 zmr4i1e+24_BSX=-ViR7Nn6FA+f9Jk1!FW=2a{B)KMW&HHMvES#bPf8Lg~n)}x+Lk8 z3kjevo%^j=Qz4@!tY6k+2w7<@Lw!yyy-QVg#?v2^Fh-U(2RZRo_3_gzJB_E*qY3GK zI{MGXwT-VK6MP&l;=>z{_McbWw$-seqm)qDSpmWx9@-SsdHwRW*@B@^;xtdw(M=gu zfxn!KU_>wovE=I*fy6O=8SkUiN)KgDoJ*Tm%OY#zGEoA|6Wo&0ly04{2m#`lHE0%d zNJhAJQ!TNTnyZll%%#%$nEM$A%9r&>qu4;0)6-09s)=PFCN!#5WrU_Hh-GQQTJTAt z()rXg=GCQv6P$W!l)@_|dgm$zX*gQgCQby2Rm9+UFFl)6Tch(&*2QgKdnsf7v|91e z`L*YBFne3;29ESkpKktF!zQ=@Il#k4JJ5Y%xp>+{6=nAMKI$lII^`5Fy>0U-fnGb~ zL`8XbWC`!1)89(p8^_~+YzWaY7AdTENh#=OB;Xx*9LkN)w=OJ?$A{Gh?+)Fc3(Sl zV#U7mZa$OjL{b-b2R}^I^)SoVkE#6!`Sdcnk&7<&LW9H2cS_9Lgb8~<0^N=EqADYL zAq7Wi)BFJWFBCs+;jj}#$O2(6$v$-)9= zrh?=)MRd3w?X_4ni5wnwzdveb^>VSwc|V_aO4H*<`NLwDC3L^Ymq;5UXBTF@Zg)P_ z$0x?C^y!^4>_l3l@eyyIa6Q!C`W_S)XG7ozv3}>r^mEK9jInraaW{RwMPH?U{F>Y< zk@_fdFNRd25}`hvA@@PC%{y}XWt#Q)UU?TnQnJu^%;Se+ywKR#CKJ~~NUMRra^R#h zT6V=VFOr7&bI)GBiwcfRC+)3TQ72xomJvGIs!nlB=|`^UXq=tylI{0Nk{Nf&eN^Lq zWAnVIGlIOL-@)Kd+VY9Y&O+9@P>!Ejb-{|hy5wD>b1!h96uVEuxzWMCqbAMkR ze^Q1nG8&~4ATaKvFCGlMmN$gXGL}|)Br9)$HC&jsA2+l3(o7|#t4$1AA`4-JBETfF zWWGHAN)f9QWL3)!kKg{!7oh|4qa9%>I}#biTK){&PB4&Gml2cRUP{fI^>g5$;~ZwU zAjR}iNPM=ioc_+1N6|81KJ?J9)HXucUs2Z8K~XY)Rfd(Mt=}(3Wv(OYl)Q=3opLp0 zv;sG44Zy;E!@#TOVdrLRIz^!KfklJ(E9V2TOCn@<2&T8 z2?>hg=F~zdS(L1mtQ^Rf*6O3QO;+%g2e#R`;@~2&i^u}%X52tD2`vm?2J)B`X(&KI zNhlQLOHHU{*Me=~80%ywzH`s(?zp_AWgWLZ4a%LSI0Todku>9oBkFOqD^cF%s9q?t z7UU~clst|W@T#(Urt#yG9F8@i?!=poeO!;2ne2u&O3*bp80iJA?Tun0im!G_{Md$^=Rwd8EsLPN7c0*vC~75*(mB7V zttp0uH`O~Oe8--OViKJSF-h-x^#jRs;u$U6A382vizW2M-CjNT4)oJ4Ztb{EaT~9a z?o}AM^&uS<*IO1^1j<;gAJ`C5fu1houJTtImR_#7o)bwz_uNO{d7D6zk@pG?;Anr% zCw|&2S~xf^maZ3wV+DDG0$FSs`QijbK$fYAiew?1(Qy17`CZ4;sMdckzciC`=cnNJ ztq}O|y!ws}UA~VuWlH2eiZ67GbfFz{)iI)LS?Gv0!9p`738o`5Eg3*qh>2>3p0wi@ zEb*gbx|gCj^~!({dpklnSX>%;(|c_)#X_0u=Ss;Po?e8JlojuL z=dweir>4_oB_!j_Mot@k9W3P8?_~VuiBO!r#3i z_4-2#3=6F=T#dn?zhqsOoj#{mg^p<+Qt`M7&Biy*yBi*g^k&#(S z->$Qf63BASQ>&7oPWVDBowm|=_?#ov(lepYx40{>X0;x{~^TAn05+Z zi4&@Kza%Ca{h!#>8^3|s6EL?FgCpAX+`I@#=0rhnw*b^_f##4h!?%-{HBS*(7tZej zHKCNSAnxMf=9R|2R8i!DNai~C^jFU}KW=?`wV@sgug#4I7xG?hsYqUU_8*aXmpLT! z6wAIxAUkz0QRn;R!Lh(qW&>6!rE?h;Sm@v->VpoN6g`=td?#BCbQzeWZDX{dIQm(~ zgWBTDdM8+T;3x;a-R+dc1l&>m=mFf|O0^%C91gVeqwuR?e&2ZbBwoc<0nHju zJiaOm?1&Ea3uazW7?#?_;m=i}sn<2Skw6@0h=Dhel-C|!E=ys_z~~xpnzfalke?!X z3$C$+svqa}|KX>9{NbmroUJ8bH+?$DI*Bf_CAc23Qv~&$$LCMvH4Y9~4;A$<{65LT zBd%w`pjJj6Ocxm$L157I3XK+B*^RLF#XKT7evEejzRuGetzPZDj+6uE&0WEr8w;T< zr^);IYmu*lr*l7y+`JG0zAH}yI7e@0xuj(^XT&attDag64FIu1d>*r5{TQBa>FeaIfOd#wX^vSwpx<`CHbHOtY zY2)HR6KNeF)*WMGc$_tuwD{eBf8?l;xh7&sXutv7!0_j`z#?GgEsn&(nY+P4_+kIJ zku1|CTO4=z>ZQlBx)~?*DSIn&XFU19Wv~{^z~s#eYE9oQ@b#)(#mNeisSzA-5360N zP746bdyNF+Fnd7+0U>*>?BFQFCs=7auD<&q3ELt@yqF8`5Ed+{qT6m(wuD!0A%!=&i3{IZ~FGJL)Lh;-V) zjroEo$S&^^-w5^3&c0)Ervi@Dt#DR?d3>8Yg`Q`&?t{yjh&w!xSMc~U?{=w>W%ge0 zhSJI5lGZ5~<2N2}ZEP2Ptq-Amy1(}PdCsR=%6it`dvVNosd2zs23Xs1)J%}osi~}k zFyJBsUQ2Db!2vzf)%{B8^2ur<>^tM%qP zn_m-Q(fUGJfP|(lj$lYR8Szabq&}jvDo;U@b8&N1`~_+`h@60M&Phop$iGDlX0LxL zDU(Z7_&j)OnN^rssvjW<4dYEf*X-zys08J%eGgE{alJ1@tTy)axN)-Sygt3(64}AS zn=~Z*XG#S)U}mk~XsUVi@LlNa-9J+^FGu*+-}lF&r~5;Hi0(BCvCmCiSYUb(Zm zyteu7KR-gPOe`f#A$^FJ3nP}(Jr;4NGu6(Rg{yJOFAL<=^Yw|GAlW#gDDkS2w(j+S zW?}IIKj3zrmOU(5L-!G-*5;mILCCGb@G;E@(>rnK7wZLWVH(W>-l@Q$wzL3qN5!k( zXYF3%S_?|0ZU|0mDS|;vhGJht49$|o#1O9-YF&$C`M~}V6Jsyfbb`L)YQSv96ixAR zatz(*>un)cDl%xaoQ!m6RrpKfB$q_3gWNHk+RPw%%@tnzruB zGk+*?FB?WCvPPf?5LOb6Q1lLVNURHbOB(M~-cTLMwmtm(PJ*TE@yfd`(W~g5{Ku=%B=GAiRC(VohC#Ln!OJ^n1l}9vyYAITMIZIijg@F8nQvbswmTGA{pH+ zMLejr0IhoVT|aj|vO^2XAwq)7HD(hJv$LJ13XCGiEHv^1Cpup`!IT*Z$4~Dj$E@=L z9%XWa+JW9}9F*wBclq9W`&eXh^MYm(Jg*h|c!K&kN#xew)3@C4w3%?4{;>I7;az>G zifEKwr8Wb9xyKf)!cPZN7lpUpm{JV-zM4{WfTZ zC>p+a@<02SvAZ1ScmChl|CrsW{|)iqu;jn~GIrMo zMc@8v>)j{M|0Mt0CyckQG^dIL(7h`R?z;gpu;Ym$ug>T19k-SxStuj=1TYq==1q>t zuB|jKCpbk3mWaF|gPUmq|HNu4ebJe*IJHz6x*RfFWia3nG-fjUe2D~O(j+DU8i?7C z4%@+cYI~OQK182if`R(?x4i^WU7YC`Uo)p_m9F@xT0DVi3H3{?!6tFqQ@snXI(?@2 z!=;j2H@veBWs~*z;Kwqq6gGah8^~J8)^0fD-4$2{%o>{vw=#ml82+=j+N_84y*^s6 z(5@SajPSm{D6WMtv$FgT5oHPQXlg;m+Y%W~d@NYkQ>yO_Hpz2<((wy1N8FRZ57Yn2&70C$i z?3qiAvJ1!b6*Avk2`d!0V$UC$FG*B7rd<|@o6}oBwH}M4gKAG! zBJL#78&~5p6`1V?Ym7)bF0QEm1UxgADG+)-j<_VngeXMUK1$hcl5>ZgV{O5tMr`kG zVu-l{P59Ao@#?K#!$J;?-X7g>Z-4`c&TkZlC6jUdrNJ>yRodj8# z!&=9(Nb`V~*fF+E>*lM0r2qotDXV0syhIZD^(*aPJpjedVX%5-Z;Av#+ok2^1 zQPTLiM|@ZZ4t_pLK_?R9@-b0+3sU;F*H$Jvm&Y2@du@_>5r!}bLY%5Q+G7zJnEpMLzg_1{^aln#YZLjhXhWT00e;OsmJL@^rc z9yt}bT!UQrjXzd8v_Vu988zk=1cgajOWF~QL{lbI7_6zH*p8i z%SE+H9ajU{YV#hk27yjcZe))lU*P@jjC3e(_(e?y!L}K-HFl zk0MN%)u`@K3hEro&Sk&h5X|)-JRn8~`*}_uQTsvQ@#Uh%8H`czYKKgzRcC8kJCl>4YrQFX&nS&$nak_QU^pkJ8$K0vwpMf-Z zA@V{&=Bs?vLQN0vrjzD4c+IME2RS*22K&3)Ek#;iO0dbL4bG`fRLhcqmj( z>~3tPT5oEi84~9841>GHVQM2q7ibbont&f8sh`Xk(?$2fIFW}<<#!Nh9L!Rjl%7We zX>DSmfD^Nzrxs>{y1P?BzAuWo&tRxa_V$e@rCswn7+5NK$&_Y^Fd`P4x;y+4nvN)S zyDObzEGMl}kB{yWWbI|5i$q~B#FamQ3Ul#7PF-vGQtQ*MRLKq1)VO3wg7L2>Rz>yk3Kt_SoV|V3Y?5_JFxFC}m0K)Zm`|WvT`4teR&~hrU zWl)sHt4pe+13Q-_ZBR$!j(n6qb8NUv=Z3yl3c05(cLgGjhD0wn2}SG7wCM+kr2fq! z#XiWb(zgq^SpwOW>&f>{XJ6pSUo`pz;!xM6@4vXS@nCU(8f%Sa>;sPKnCBuOdES{!SV0{xT|xT*#qS67jyvqH0146|q z0znJaMHV0Elex4;tqg1LdPzDxK~$X0C|moE9hSHYJE{9wF` z**}8Nz}E*@XoBLpAx7q%pB7<#-G=k{{*#S`t*0O~1zA-I%~G~JcG2aU6>HuUPp1}Y zZYoxY)5uMve0stkxdNY?1KKPM>%x{mnE$gy&N8I$sqjN<{ zVUz1EQ)>LYEOu@_4*h8G>E71R`u^5iFk3iIavfyWt#ZtINN?3~0w}KHi%SsEBM533 zH_!th-)PCk90Z9H84$?B${%8gM%5?S3a>=WFh^rC4kW zenw-HdWow}DE0LHYf@1t{t5dUpnYrW5i77-`{M1UGRm1vIvL z-v^_y>skCx(q(|z7&Y1Zjy(p#Su=KQ?< zkpFyIU+Wn?rVAa3%w8lGI}uq|4gBL&eP>G0c_5)2(uR;i1(_RLvC|u=4IN{)Igk#@ zT#$Q_xK|5Nq{p0h9h^pe^F_BwL1L6I+p;WKE`P3p3MZGKu@(%Czbi71x_V}WW?HC%W)#$7eBeT6~43i?VXM0oMelVuh}`h z)Z(bASN4wYQWz2k9vGeyxotd{W~R>Jl0g64=La7?NaV}sBb%hwQCEc~@wi@n&tZRG zp9W?IT%~rzRe^pnN~mP35SOakRWT1Fwd$p1y=WSkPpm0JNtVKw1+*TKrYsBz@0}*0 z9#m!HU?7zxZu9E6HOp#5v9FuQuU7(G39SncrTV`6UF$!7hEH(k2|Vy$Ki+(sUyqXa zG4l(CL@sSvZ}=n93uF?be~~gUPJtpj4s?MeV@A+15s?`nf*GrSR^85L1I015^Xcw6 z3J=)PzIPCCngoo3Fs0)M5;|2{@F5@d!`QEoWdH$`QC6}ZJy0|hjjL4bg#BX(RrXE< z5N5Dt&}R$vwu?S2wHyu?pAhJ8xFl}my3vz$Tq;nfP(urQSh-GPLQ^y$Y` zfV1SjH~K%gj|-~-T6!)bYe zU`@bM8-!6%E#zF}8qylN!ySAW*K#x&F!`vP(Q`FmMh3KYMVDAcjt>yiAWh9j;hpPJ z(}aiBS|MTCe{X@Pj_6+bmk$vA*Z~X({j32)KbnC}2x1Qbk`A=f}!(12$q_|Q~b@2p77Mfq$ zar)QbKb?~2q3#n%n_Ng`n-#WrQczW~3ZGM#eA}k}tCBEn3+|MQ1!UGkXKHYqSxzSN z)kSB1jT$rT@E(T(DBFR|7a7l-zzCd~>`*Lk8rTdDnGYjWn@NN!JZ?!hV)h@YkI%wA z>|IeS*OOo@%!?ue%T`FhTYviE4xnBSqTmGj!%z2fBr7264Vm4071l)SMQ(B5>r%S? zV$(9)!HuBr2Tm(3J^Th;)`j=O&&P&gp`-eE?k=(u%@vdaxE%`=`1K>5^F2Ntz2Urm zjV7`d1OSiYXm0TXC@qAh4~)MRKA6S+l?q*L>A-W z769{&H+NJEhM1N#vJvhNJK#(hw^UjJYMfQPUjW=0js&A~G|7uEC_Z7P2fIeQ7EyE& zn5g`(expD4{V7=fMX_mXADqXs=3w8_vTywvYb&=AHT&Z^C z9XBKN?B#Wyl$@InPL~|*p@2SW6s|#h=Y+!rKr^Vv<9V`Dy#+lbq*s4I3%j`}N%S-&_{Y`R$&7`IAzQ2u9oS{0Jj;j(vUU>8qsV za>0OQskzge#mE;A*?TNtX=RGYL}8ktH)jA{SIaE7j;ctqHX9h`Ja{dj3ilxM`mHxt zLsg-w8DiSH4rV0sx*=tlrgoI8%ix%~yM2j-GeR*5PN?sjiHI914X{Rph2nsCHE{wf|ok z`uTPl`n{`RKzxWzSRC(=0yrA&~Uy7@p zmtA=Jin_;6R3wSy&?O>^E!m~mFt|0b%Lb#87zQ6$VnGSG*ZBAWL}&D$SWSbFWgr3u zQIMAty_^s)%P3LzEX?znx)m^pKc%_#&{X?KR;PrDMIb}gdq;FrtQBR4-~O)$+z)nu zAR&488z5X?N{6`a)Zxgcvkr(@QboM|G|_rIHa89CtQf~Ztf-?w`K@4tG1&9^)>L4u`8 zx_1obGSf@@ItoD;g`sxJg^uJH7dPVf=d*dq(cZU$O7)+JPRwvu6N&cEBEuEC-~-3^O*QP>pnF1yQO9xK<2n8P7mJP7|km((9lI)TBWm zs7En(&BYhhdZ3{9&tiJZeNLUm?RuEL8Tj&FUD)mZLZ_q(Y#TmITc5DqSjswuC&Y}9 z@;a9!QlTzgW*ko{PgXQ#J0%7mWJV7tIwQf^D9=j~-fTSF7D%DRFpZ)EQg=aOZ zc$|bdwL>0&qFy56XU02Pj%mPdEgo}u^vLjv4XU|8A1HQkA$>8UaJm)~^+kbG{A#D8 zqP~-I!P`=yWX&q|0)pUzcI3Ly%n}JQOVm4PUZF;pxY(N}$jsN9k9h2uBxx^h=CR`0 zDb0_zetNV9@rHAEwjuYw!!o&)dhY>0AldWkI|`#kMmEw3sItdz07xe!;|)!aDGw_{HTg($|MJOY% zGvOds!TP8|c4**K{r~#$`F=1q*?RW;e$H0-$+@3jOke0%EPX$p6?ykrRF;y(d{Hp% zE)h*<6e_=@Eljg1emW+Xwb3FdqLZ!7gDzAhp;8fJx zRa4qW;Xu;ZuY#$x;g%wV(94s9Ulb!$pzLsPNk&1D@oL3;XWoz%L|F|{d9>knOGnSv zM;WpL&ScB4n!_JaMv@9>aNeY|@b?0pCpc=z`~{!hy7fP?W&@ojij_9Jls(*{3Zd?H z$4&5l+_kJGFuCQI30EY`?Mc>!&qW(Sg>Yntf@ z{kwoWn9+=si8nT%e7QV+sSQl<2U^2-p1-a*{0cfqnal91k%vn}FRt&1wVLY$cvmUS zcuF4mDh4*f=og;P^ehxrDw?3O<$$$z+k1`6oLK8vo20xD%PyV#^YxS~=Ej@fe&BW@ zuin@NbfVfCXATlMQ;8{O z^G|w1%)!*cNV3l}cIU^2t=rqr2pj$NA8+4j6}c+t*Jx(;0>zlo&={Dz-qs&oxR`TfW*;-X62&L-Cp(5`ZD0MTZe zinVzCGQrA_Nz{W+0{Vdt8M~jzlr8im%JS9P0g_d@F<`KD7ifZsg2@b{cE4JiY#?fL z;yC&NC&iVWr-Az%L>C>lMnPM4!^IKRU&o4q3B%w(-YKc)Fd9IP_@||k%eFnt(hJYN zd-h5zfJn>b3k_QfIX_KrJ*8fwqa6*(B@tb+TR4-Zs85xa1&77?U{ZDU>pu!d+cHogrvh=KaS7BZHzR#|i9$w2 zlZs@zIB`D>!#lmp=8fNqmv@tflo^Hj`AQyH5c6VYp~}5?j(th8dD<~9HCU5=Y z5_@xPYyHj-+Yh<6@woEldD2)}m>+XLu|o4DJG%F~-vl~!2^hZ5D_&*JwiMO#W7nxK zsXY0U8H>atzyQQ9j8PI!$`eqkeAjZ(WCBOWXePMoMp&Gptdg0g1E_qL6`$*&_tQDL zF$;%OGGI%|Lb5lvl*4q_n^?2eMdvB;Hq#yv<7 zSl}D3{uSH~Ks9u5bO!`s3N%wBV+A@zdh|f3#ExNOl)L#@$ttaojCW1E$uc)pXkaw- zt4Gy2Pu89XKi>SZAqNxRC`rZ8hDKeKk#Y9DvoAMbC2H&yxtLe_fkXf0fkpNr1^8?} zV&%y&NXU9xv=lFed-F?!}>rnVxE@87vRO=lSXW4fk3I@gG?GGpuNNCxPVJnP)}6xJIL2^^cm*ZEY`iNAtPoKfb&5 zx9U5aitvBRC90MhdE_o*&=JtzEWod72dMCbN)oe-uwF$ocK6Wx!u(9UlcE60guz-o za5XC(0_mOE)zdu4f>T3!d^PsN!TqA5GepQQ;;l#1L~W2hgc`S#xR4jRy2GNCH)vNT z0$mk6e)QFpyB_rVY-f{DgrQvzU)^{JbJ~uXOkFH`u@3w9oExV?>{$bH{ zQ&pl@rsf}A#b59`dY@pG@iT^WyUo3aUZ}N|u3ruq3+7V_9qo)SfF!Ab1lQ~l%DNdm z+=eNjHg~rRNml&L!zqOUaE?OK<)c}6Vsm(RLg0@!H8jAJVEm65rc_zgrS#B!TWYgb zRxnA3K)$FW2o9^=*;LLIl{@w&XHUi=keoULlBBXs+UYZcr|NU%K?n|++QX6^vK?r? zFke~ktg!cXj|LLo&y;?!JK_j>qi(VpGx!ux2b#3 z;zs(^M_hlk1?OnJ_ZvU8GFp3wYcG8_^);Jdnv#gqR%S}O6zH5wX88omPnqmA03&XKuV7$ zfV?2jwxW_3UeX=Gmf=@_tu_4VX5ihehkMCDjlR-x%`;@r)nJs-eDjxl_U}%py1$g& zB+&HAj%^X6wC%G-s2(5N#3R!G)l(6@RB1z;-Q1t8$CK@SP z!APq7{7~j%Mt9;Grmib*zfGV`W&<-ur1daX|$2^2i_n*#O5Z_(+o z_IPnLc-mj@I^SgF5Z&)i#Y;417;m#3yLqeV4eA#S&$n%Ed<{Rr8viP1bF&u0FYdOe zN)2>pdw1P*_`3jfLo9w0{#sN>)pD#~u_VIFRZphUhG+z5stWQ?z z?xjKIh)d7cK>F2^7|)w*2b!R+X3{~hV>gS8dc~4QtSl4OGZrBTp4dM?2~JRiDq_-N zOAI|PG^Lo7-1Jr{24A1t+0+Pm+J}yZ1y+Mql!^7!@T=mzSzqjw=L3~Wm{+R+aJAi{ z?Qp+x^*MfN<6GEo+tp#xPtW%&lNu{~2SRA@0y{p~DbtB6C&Rl(Hhyuob9`72!HhOY zWb*1V4i(Xj8!yl5qXcb5NkbgPQiU9=0s(4Rw92S15~$lCi~2GY-zvychgeQ+ay1sk zDGh+z=AQK)Tj`ABVj}(^fkw-ltdplST!es#grHji)?Q7ynSwZ*{z(f}z8iBJ%b+yQRiY*A(; zB*^ zFZl`Sbd+p4-KD6vIa94v6`hCj$uFs{~7=ip# z&ieniSo!M5hFL`#*6eCQUOyYLQvaIVYwHblwmfOsB3p)jzb;>2dQmixfx131397hq zwMj0g^57`7dGLDLHB%N~et!cYwH|U^P);NQ@pAuFy4Dz}noedG7-QQ4I0q1%+WVZl zGzWJkTA-=3K=n@fNkq6Vu?X6z2{Zs#<16(yFsDXQ58T=^X?C-mS<9u|%H0+kn=Qd$ zE#x)x+Th%~8tXZtXQVD&W_QRFm+;nP>!Ne(N#M!C$2S-LNg%7pG;e#E}4fL&ly@> z&Bt|=LiEgmwCZcHSUD&Z*Yr~0?SH7TMJ`6B_ERfF;|-fPHlDzG3T6oAUyw*2M?48sGcGaTQO5@5VLSL1K3bLwSL57uWhO5Z$EQ zVA4yfU?0CboUge>3JkN25;#{^#W8C2NCmn?l4cdETv;5+PJeInyvcL=Fd5&s4ct-X z7}y4qX#7SJm`j`T^;?lr%mz8^D@0>hKU5i5(i3W*A(bo#|uP zC=yed&2pfPVup>EE1fqp=b0BZ@lvrSR61XSF0cs{7lOUiVHs7)qAJH#IkW&ZKHk{X z_C_mBdt>g{u`SZ<57Ok>L0?gO?cAptL}1QjoCQ80UCYXDy)L>`s|$^BI#!cf z7~mXy_EAnY1ZBFPR&1_+*#K0w?C5d%iZE0V1Lg{Xu-EHA@LYS(+b@1G-#b>GL7+b|zHal%Q?5jR2%{3|Ox)=C^H!#uq+I>~=-B^pm(8g`+nj5e+CvSZB z^9<(0*^Tzv8<@fk5;sXo!PzLBhW{M!8ZH2HgDrF z(I}A@&Uj^=tKRLx_Eih!J|Q;5K94R$+do)y?jz-&8}EC?qYQ3_j< z;eC^IYrcN@2nNC&Ia5iKWKDuS-Xh`vIl$jV+NTZt!&X?MWI2zHWi=!>7Ac@$7`Dq#rnZ~v$xrW3@@-t;Kd-{KM%AtJIX`RV9rVhJFg1) z7@aF3UX&eA%;|kq!k8hVjVG8&u+Bljg(;%m?qmVZ>x6lEj38Cmj;=%tr|t!IxnYoLnx7)+Y&$x(NEti~Qt!{)FjKe* zJKI;EeZH2X3gs!YIB0*L;R-aENcTDYymIlgsHj&%`CV*!*J-|IcLwUoh21j&m7B79 zL!7pfylhg!!K|FffbamsmOGbWgg#=voz>qrz!GG` z_vORnSS%}p)mU_VV*hnF|CQpo}(I@Mssd4!AR<_iuGkIE!peqDsU*9%qLN7sf zh#{9GEG>HyHgDhs`u#_%-tFXhb{C622&nioN$I)s%y)sUL6C|;28CsoC&e96+Om9{ znGY1#>wCUr(=Bj1_altUvNt}TeV*a2Puj^nb81Q?LbtaUBTJZOLblqYvV+sPKscib zH7`e$jj_!blBr8s!nFc#J5puA3=rNUi*}F2E@<|L-1xk#z2s@*H81)j6T=$`UPA|2s0k^b->!5b3pzCkg0{c8h(`nWjJ9~n2xPl15;gA z918MINFX6trKdeWW+k&^g?erbM<1L8HNkEHgspI9x3g5j@BN13Njtu6-0g`r0NmKq zV&Gn_$;CBn)zv`B?29dM=)Cvri!U3#Uut+_%@VGN#uf8;t|mVcCtBvgXpbkvB;dpW zsW(mH)j;ppB%YVZx2LQX9cXNY@h{l;5+F7gUvW`A4L?4?SFUsc0!hy%DC|drZK!kG z!-f7SV`AKiL~RthDC8BM6R&hm59h7I5I9RJDrpj{r(t*pkL9?{=QlTP!Sedr#^=&1 za_SO(_2L)FEU>#Cn?KT4eD62$EPtes&~USw$-d*EQc$+2%+AGS|FJqP*Ncy^{{Z<2Izp-a}imq%u!{ zX35qCo9doHV?p2pdyjjx)_!`}9ZPag z^c$`}JcLamS#slsJ3X$!-`DUBoVR0jXZL0n=A-EuQ}|YQ1VNxukj{Lti|O8#8Rtut zcanQ0*iMR(du=Y;omt(ub(@o5^c{`F(@) zvqWA;DOTc1I`i*bX(O?}EkIq;oVQnZjXGnPF@P|9lVv(ykY_REGWb=`vq zH#`}cfa4xckv?+?n^emeh9M(h9(8GM3`o3Y8&W3qSS@4Mn`~KF2^G-i3VlUmI zo+D4SwkCzneJVoG4#?(<23D~LJ9rZph-nq&Mf@jMB2^Wsh?5uS5Zfp6!1#Tq-`x4_?I2|7ZE| z-|OE!682sl$fjMGC(^@ig0$00@kNfa#fa6UKKgtM8j26qz+o>qfZX}|Fqy0iYDdxj z@?Lm!oH?aQiQ{4w22I23dfc|x*hB;gEs>x~k*4{MRx!_tLX5uNh9V5cQFZ<<+NB7d z5+;E0iu%cVjbs6MppG8XrTP|9F+I|V7Z6k~gH4!`4s$E@g+EXOWcb7MuQCDf1}7XU zb`{k zq*^>R2PeA>-yc38vK`eEGM1>j9K-DKbE(2lM?DE0o;P-BC8^$;@k6#BM&Y>mft{*z zttR#+Fmb_gEcgh9rSQ-1x$|r5U%nQd0mD_JByGtiY-IQMky?9io96knm494*(qcOI z+BSj|dj*lYCTpA0R@pGdA)}ej9#oR)t=R=(&Blm8BH6k_MaEyVXacH0>o)&mP9SlZ zoxDU^udP)R4hgyLaECD`tV_P zc>lqPNwXxu@xwpwnXeUTK)NNlh9MOQ?IOvgI@w6Nui7?*+>8nX4ot~KYNX1m1x7cJ zCpmBx2l0F#eZRD6{`qgbybUST_49DSlOl*qn41tlU(ls#?NRs;q!%Sw*+-P6DY^RK z2oi6h`e_1bW_q|ZuUDMNvO(NRPKDOr&HhiI6bDNo72E-i(hDLqo2WasZxoV~tC|HV zZ04iOx*vc4XO|_r3{SYFrsT>c6O#%;VK(WftHJ&HPwAV>aBmH*y;v*BVvdtKG$&*y z^+iUeni=>9Hg7g5nhmm1-zUF}k$Mt@2y=J(;OS=u@_tfTzAY<0cLgh*>KS&KS6dW; z69j129mlfB9618dR1fgYcU1$Ro#1LFx*G#KG8{Xl*=#uV@9p&cw{Mk!dj((V=mV!iC1d-`UEXe!>il9ak?N=uD# zJ$Uw3&iaG84PUU^`M2k*v$`~g>SToO7AIK+RatfnS1%AbD#M@r`Nn4-n?x-nh5DrA z2b*W5(IjT*jm$B`m&d1AkDUW!uCKg4$GQg~{LpRzzo7^79FE@%7T$v{NrNW7N>FfbDc zlpy0gJ`i>COf?qtT2BW?w^2@xY#U&;&(35^>j2gpF*KRHm+!5`>O ze7dU;Sb6rzm3({F{L_D`#8oFUu%2|E^Zbu-5E$6-y#i0QGe3MZd*&k1=pejG%U%xI zY0h3QDi2BW<9nyu862HuNy5WzK`rm0fG-4P|6tXjSw`BEq}+zP`7@&A_NjO|I3Zw;%smRDyrS$%v&3Z@$)l($S2mRP?ts z*e`DOJoz(B)ipnmaPj@%RSol$U>DS|w*HhZo`-9Wj<2W(iw`nGV*=z!b*;Dkwh60I zimL{e=ip*3g4f{S77DGRh~qf2!mMp0#gxm%A_f}IQuFk_Uulay34zgkI(`CwpJchQDou@2jMA&cJ2)<*zgQNvDOeV5WT-<$gyAM_YV2-?e(ZX3b)HlZNVEWaO}VuMAPct1L#@r{)SUqw z*%k`X3jb%9Uvs?@Uv9`^s_sMJ0Fn_uKD@CJNZ;t&eEJIQq6?EBzTO<&{n{$o0`T9! zC|x2_0_3R$N$(C-uwP{;zhtXY(if5(l?UXEv}_RZ(%o6l}+EZl^z z{=J`8XV=U-$oL1|gwmU>Fw3F}N~#sYi7a1lmZWigna3@^>rlmAx%c(5$=l{KL?G70 z^dK=|lM}jO&_4|QN&nqVuf`@xy)~M2?XUc_W9@rB{H=G=ATu2HtI0?C^E@@iU3WxkUXW8*yWcuEis&6=zmCRG$>7}5~BpS+t3A2?7 znJ;h)CNa@?&di6vh5&LL;3P$&Ws>2_X#e!>uGJtB<5mw+Eg!1U!JccXNLQIQSk0^@X^%jMpWpXEH?@IJ zIpZP4-GR;#ajcSaf&^zsg5CGK zH_rYI&Ir1ojP}tU%WVrk)UwMc-UBrH({|M(K<4OVG5h4giGB$K!HJwI-^3LN>$V4A?`8*sbO|yn0@Fp* zwkQD+iL=c07 zBF}UAuo7>@H z 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%0A=7Pd}_05 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 F_8+!`Bmn>b literal 0 HcmV?d00001 diff --git a/modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/Contents.json b/modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..c3bb428dbd --- /dev/null +++ b/modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "App-Icon-1024x1024@1x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/modules/BlueskyClip/Images.xcassets/Contents.json b/modules/BlueskyClip/Images.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/modules/BlueskyClip/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/modules/BlueskyClip/ViewController.swift b/modules/BlueskyClip/ViewController.swift new file mode 100644 index 0000000000..b178644b8f --- /dev/null +++ b/modules/BlueskyClip/ViewController.swift @@ -0,0 +1,133 @@ +import UIKit +import WebKit +import StoreKit + +class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDelegate { + let defaults = UserDefaults(suiteName: "group.app.bsky") + + var window: UIWindow + var webView: WKWebView? + + var prevUrl: URL? + var starterPackUrl: URL? + + init(window: UIWindow) { + self.window = window + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + let contentController = WKUserContentController() + contentController.add(self, name: "onMessage") + let configuration = WKWebViewConfiguration() + configuration.userContentController = contentController + + let webView = WKWebView(frame: self.view.bounds, configuration: configuration) + webView.translatesAutoresizingMaskIntoConstraints = false + webView.contentMode = .scaleToFill + webView.navigationDelegate = self + self.view.addSubview(webView) + self.webView = webView + } + + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + guard let response = message.body as? String, + let data = response.data(using: .utf8), + let payload = try? JSONDecoder().decode(WebViewActionPayload.self, from: data) else { + return + } + + switch payload.action { + case .present: + guard let url = self.starterPackUrl else { + return + } + + self.presentAppStoreOverlay() + defaults?.setValue(url.absoluteString, forKey: "starterPackUri") + + case .store: + guard let keyToStoreAs = payload.keyToStoreAs, let jsonToStore = payload.jsonToStore else { + return + } + + self.defaults?.setValue(jsonToStore, forKey: keyToStoreAs) + } + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { + // Detect when we land on the right URL. This is incase of a short link opening the app clip + guard let url = navigationAction.request.url else { + return .allow + } + + // Store the previous one to compare later, but only set starterPackUrl when we find the right one + prevUrl = url + // pathComponents starts with "/" as the first component, then each path name. so... + // ["/", "start", "name", "rkey"] + if url.pathComponents.count == 4, + url.pathComponents[1] == "start" { + self.starterPackUrl = url + } + + return .allow + } + + func handleURL(url: URL) { + let urlString = "\(url.absoluteString)?clip=true" + if let url = URL(string: urlString) { + self.webView?.load(URLRequest(url: url)) + } + } + + func presentAppStoreOverlay() { + guard let windowScene = self.window.windowScene else { + return + } + + let configuration = SKOverlay.AppClipConfiguration(position: .bottomRaised) + let overlay = SKOverlay(configuration: configuration) + + overlay.present(in: windowScene) + } + + func getHost(_ url: URL?) -> String? { + if #available(iOS 16.0, *) { + return url?.host() + } else { + return url?.host + } + } + + func getQuery(_ url: URL?) -> String? { + if #available(iOS 16.0, *) { + return url?.query() + } else { + return url?.query + } + } + + func urlMatchesPrevious(_ url: URL?) -> Bool { + if #available(iOS 16.0, *) { + return url?.query() == prevUrl?.query() && url?.host() == prevUrl?.host() && url?.query() == prevUrl?.query() + } else { + return url?.query == prevUrl?.query && url?.host == prevUrl?.host && url?.query == prevUrl?.query + } + } +} + +struct WebViewActionPayload: Decodable { + enum Action: String, Decodable { + case present, store + } + + let action: Action + let keyToStoreAs: String? + let jsonToStore: String? +} diff --git a/modules/expo-bluesky-swiss-army/android/build.gradle b/modules/expo-bluesky-swiss-army/android/build.gradle new file mode 100644 index 0000000000..b031cde57e --- /dev/null +++ b/modules/expo-bluesky-swiss-army/android/build.gradle @@ -0,0 +1,47 @@ +apply plugin: 'com.android.library' + +group = 'expo.modules.blueskyswissarmy' +version = '0.6.0' + +def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") +apply from: expoModulesCorePlugin +applyKotlinExpoModulesCorePlugin() +useCoreDependencies() +useExpoPublishing() + +// If you want to use the managed Android SDK versions from expo-modules-core, set this to true. +// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code. +// Most of the time, you may like to manage the Android SDK versions yourself. +def useManagedAndroidSdkVersions = false +if (useManagedAndroidSdkVersions) { + useDefaultAndroidSdkVersions() +} else { + buildscript { + // 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 + } + } + project.android { + compileSdkVersion safeExtGet("compileSdkVersion", 34) + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 21) + targetSdkVersion safeExtGet("targetSdkVersion", 34) + } + } +} + +android { + namespace "expo.modules.blueskyswissarmy" + defaultConfig { + versionCode 1 + versionName "0.6.0" + } + lintOptions { + abortOnError false + } +} + +dependencies { + implementation("com.android.installreferrer:installreferrer:2.2") +} diff --git a/modules/expo-bluesky-swiss-army/android/src/main/AndroidManifest.xml b/modules/expo-bluesky-swiss-army/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..bdae66c8f5 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/deviceprefs/ExpoBlueskyDevicePrefsModule.kt b/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/deviceprefs/ExpoBlueskyDevicePrefsModule.kt new file mode 100644 index 0000000000..29017f17aa --- /dev/null +++ b/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/deviceprefs/ExpoBlueskyDevicePrefsModule.kt @@ -0,0 +1,10 @@ +package expo.modules.blueskyswissarmy.deviceprefs + +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition + +class ExpoBlueskyDevicePrefsModule : Module() { + override fun definition() = ModuleDefinition { + Name("ExpoBlueskyDevicePrefs") + } +} diff --git a/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/referrer/ExpoBlueskyReferrerModule.kt b/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/referrer/ExpoBlueskyReferrerModule.kt new file mode 100644 index 0000000000..3589b364e0 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/referrer/ExpoBlueskyReferrerModule.kt @@ -0,0 +1,54 @@ +package expo.modules.blueskyswissarmy.referrer + +import android.util.Log +import com.android.installreferrer.api.InstallReferrerClient +import com.android.installreferrer.api.InstallReferrerStateListener +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition +import expo.modules.kotlin.Promise + +class ExpoBlueskyReferrerModule : Module() { + override fun definition() = ModuleDefinition { + Name("ExpoBlueskyReferrer") + + AsyncFunction("getGooglePlayReferrerInfoAsync") { promise: Promise -> + val referrerClient = InstallReferrerClient.newBuilder(appContext.reactContext).build() + referrerClient.startConnection(object : InstallReferrerStateListener { + override fun onInstallReferrerSetupFinished(responseCode: Int) { + if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) { + Log.d("ExpoGooglePlayReferrer", "Successfully retrieved referrer info.") + + val response = referrerClient.installReferrer + Log.d("ExpoGooglePlayReferrer", "Install referrer: ${response.installReferrer}") + + promise.resolve( + mapOf( + "installReferrer" to response.installReferrer, + "clickTimestamp" to response.referrerClickTimestampSeconds, + "installTimestamp" to response.installBeginTimestampSeconds + ) + ) + } else { + Log.d("ExpoGooglePlayReferrer", "Failed to get referrer info. Unknown error.") + promise.reject( + "ERR_GOOGLE_PLAY_REFERRER_UNKNOWN", + "Failed to get referrer info", + Exception("Failed to get referrer info") + ) + } + referrerClient.endConnection() + } + + override fun onInstallReferrerServiceDisconnected() { + Log.d("ExpoGooglePlayReferrer", "Failed to get referrer info. Service disconnected.") + referrerClient.endConnection() + promise.reject( + "ERR_GOOGLE_PLAY_REFERRER_DISCONNECTED", + "Failed to get referrer info", + Exception("Failed to get referrer info") + ) + } + }) + } + } +} \ No newline at end of file diff --git a/modules/expo-bluesky-swiss-army/expo-module.config.json b/modules/expo-bluesky-swiss-army/expo-module.config.json new file mode 100644 index 0000000000..730bc6114f --- /dev/null +++ b/modules/expo-bluesky-swiss-army/expo-module.config.json @@ -0,0 +1,12 @@ +{ + "platforms": ["ios", "tvos", "android", "web"], + "ios": { + "modules": ["ExpoBlueskyDevicePrefsModule", "ExpoBlueskyReferrerModule"] + }, + "android": { + "modules": [ + "expo.modules.blueskyswissarmy.deviceprefs.ExpoBlueskyDevicePrefsModule", + "expo.modules.blueskyswissarmy.referrer.ExpoBlueskyReferrerModule" + ] + } +} diff --git a/modules/expo-bluesky-swiss-army/index.ts b/modules/expo-bluesky-swiss-army/index.ts new file mode 100644 index 0000000000..1b2f892494 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/index.ts @@ -0,0 +1,4 @@ +import * as DevicePrefs from './src/DevicePrefs' +import * as Referrer from './src/Referrer' + +export {DevicePrefs, Referrer} diff --git a/modules/expo-bluesky-swiss-army/ios/DevicePrefs/ExpoBlueskyDevicePrefsModule.swift b/modules/expo-bluesky-swiss-army/ios/DevicePrefs/ExpoBlueskyDevicePrefsModule.swift new file mode 100644 index 0000000000..b13a9fe3fa --- /dev/null +++ b/modules/expo-bluesky-swiss-army/ios/DevicePrefs/ExpoBlueskyDevicePrefsModule.swift @@ -0,0 +1,23 @@ +import ExpoModulesCore + +public class ExpoBlueskyDevicePrefsModule: Module { + func getDefaults(_ useAppGroup: Bool) -> UserDefaults? { + if useAppGroup { + return UserDefaults(suiteName: "group.app.bsky") + } else { + return UserDefaults.standard + } + } + + public func definition() -> ModuleDefinition { + Name("ExpoBlueskyDevicePrefs") + + AsyncFunction("getStringValueAsync") { (key: String, useAppGroup: Bool) in + return self.getDefaults(useAppGroup)?.string(forKey: key) + } + + AsyncFunction("setStringValueAsync") { (key: String, value: String?, useAppGroup: Bool) in + self.getDefaults(useAppGroup)?.setValue(value, forKey: key) + } + } +} diff --git a/modules/expo-bluesky-swiss-army/ios/ExpoBlueskySwissArmy.podspec b/modules/expo-bluesky-swiss-army/ios/ExpoBlueskySwissArmy.podspec new file mode 100644 index 0000000000..be4b0eae45 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/ios/ExpoBlueskySwissArmy.podspec @@ -0,0 +1,21 @@ +Pod::Spec.new do |s| + s.name = 'ExpoBlueskySwissArmy' + s.version = '1.0.0' + s.summary = 'A collection of native tools for Bluesky' + s.description = 'A collection of native tools 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' + + # 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-swiss-army/ios/Referrer/ExpoBlueskyReferrerModule.swift b/modules/expo-bluesky-swiss-army/ios/Referrer/ExpoBlueskyReferrerModule.swift new file mode 100644 index 0000000000..fd28c51e6f --- /dev/null +++ b/modules/expo-bluesky-swiss-army/ios/Referrer/ExpoBlueskyReferrerModule.swift @@ -0,0 +1,7 @@ +import ExpoModulesCore + +public class ExpoBlueskyReferrerModule: Module { + public func definition() -> ModuleDefinition { + Name("ExpoBlueskyReferrer") + } +} diff --git a/modules/expo-bluesky-swiss-army/src/DevicePrefs/index.ios.ts b/modules/expo-bluesky-swiss-army/src/DevicePrefs/index.ios.ts new file mode 100644 index 0000000000..4271850866 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/src/DevicePrefs/index.ios.ts @@ -0,0 +1,18 @@ +import {requireNativeModule} from 'expo-modules-core' + +const NativeModule = requireNativeModule('ExpoBlueskyDevicePrefs') + +export function getStringValueAsync( + key: string, + useAppGroup?: boolean, +): Promise { + return NativeModule.getStringValueAsync(key, useAppGroup) +} + +export function setStringValueAsync( + key: string, + value: string | null, + useAppGroup?: boolean, +): Promise { + return NativeModule.setStringValueAsync(key, value, useAppGroup) +} diff --git a/modules/expo-bluesky-swiss-army/src/DevicePrefs/index.ts b/modules/expo-bluesky-swiss-army/src/DevicePrefs/index.ts new file mode 100644 index 0000000000..f1eee6c282 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/src/DevicePrefs/index.ts @@ -0,0 +1,16 @@ +import {NotImplementedError} from '../NotImplemented' + +export function getStringValueAsync( + key: string, + useAppGroup?: boolean, +): Promise { + throw new NotImplementedError({key, useAppGroup}) +} + +export function setStringValueAsync( + key: string, + value: string | null, + useAppGroup?: boolean, +): Promise { + throw new NotImplementedError({key, value, useAppGroup}) +} diff --git a/modules/expo-bluesky-swiss-army/src/NotImplemented.ts b/modules/expo-bluesky-swiss-army/src/NotImplemented.ts new file mode 100644 index 0000000000..876cd7b328 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/src/NotImplemented.ts @@ -0,0 +1,16 @@ +import {Platform} from 'react-native' + +export class NotImplementedError extends Error { + constructor(params = {}) { + if (__DEV__) { + const caller = new Error().stack?.split('\n')[2] + super( + `Not implemented on ${Platform.OS}. Given params: ${JSON.stringify( + params, + )} ${caller}`, + ) + } else { + super('Not implemented') + } + } +} diff --git a/modules/expo-bluesky-swiss-army/src/Referrer/index.android.ts b/modules/expo-bluesky-swiss-army/src/Referrer/index.android.ts new file mode 100644 index 0000000000..06dfd2d09c --- /dev/null +++ b/modules/expo-bluesky-swiss-army/src/Referrer/index.android.ts @@ -0,0 +1,9 @@ +import {requireNativeModule} from 'expo' + +import {GooglePlayReferrerInfo} from './types' + +export const NativeModule = requireNativeModule('ExpoBlueskyReferrer') + +export function getGooglePlayReferrerInfoAsync(): Promise { + return NativeModule.getGooglePlayReferrerInfoAsync() +} diff --git a/modules/expo-bluesky-swiss-army/src/Referrer/index.ts b/modules/expo-bluesky-swiss-army/src/Referrer/index.ts new file mode 100644 index 0000000000..2553985527 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/src/Referrer/index.ts @@ -0,0 +1,7 @@ +import {NotImplementedError} from '../NotImplemented' +import {GooglePlayReferrerInfo} from './types' + +// @ts-ignore throws +export function getGooglePlayReferrerInfoAsync(): Promise { + throw new NotImplementedError() +} diff --git a/modules/expo-bluesky-swiss-army/src/Referrer/types.ts b/modules/expo-bluesky-swiss-army/src/Referrer/types.ts new file mode 100644 index 0000000000..55faaff4d3 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/src/Referrer/types.ts @@ -0,0 +1,7 @@ +export type GooglePlayReferrerInfo = + | { + installReferrer?: string + clickTimestamp?: number + installTimestamp?: number + } + | undefined diff --git a/package.json b/package.json index bcd5a1d37e..6577703099 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web" }, "dependencies": { - "@atproto/api": "^0.12.20", + "@atproto/api": "0.12.22-next.0", "@bam.tech/react-native-image-resizer": "^3.0.4", "@braintree/sanitize-url": "^6.0.2", "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", @@ -177,6 +177,7 @@ "react-native-pager-view": "6.2.3", "react-native-picker-select": "^9.1.3", "react-native-progress": "bluesky-social/react-native-progress", + "react-native-qrcode-styled": "^0.3.1", "react-native-reanimated": "^3.11.0", "react-native-root-siblings": "^4.1.1", "react-native-safe-area-context": "4.10.1", @@ -205,7 +206,7 @@ "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.20.0", "@did-plc/server": "^0.0.1", - "@expo/config-plugins": "7.8.0", + "@expo/config-plugins": "8.0.4", "@expo/prebuild-config": "6.7.0", "@lingui/cli": "^4.5.0", "@lingui/macro": "^4.5.0", diff --git a/plugins/starterPackAppClipExtension/withAppEntitlements.js b/plugins/starterPackAppClipExtension/withAppEntitlements.js new file mode 100644 index 0000000000..1bffd82080 --- /dev/null +++ b/plugins/starterPackAppClipExtension/withAppEntitlements.js @@ -0,0 +1,16 @@ +const {withEntitlementsPlist} = require('@expo/config-plugins') + +const withAppEntitlements = config => { + // eslint-disable-next-line no-shadow + return withEntitlementsPlist(config, async config => { + config.modResults['com.apple.security.application-groups'] = [ + `group.app.bsky`, + ] + config.modResults[ + 'com.apple.developer.associated-appclip-app-identifiers' + ] = [`$(AppIdentifierPrefix)${config.ios.bundleIdentifier}.AppClip`] + return config + }) +} + +module.exports = {withAppEntitlements} diff --git a/plugins/starterPackAppClipExtension/withClipEntitlements.js b/plugins/starterPackAppClipExtension/withClipEntitlements.js new file mode 100644 index 0000000000..77636b5c94 --- /dev/null +++ b/plugins/starterPackAppClipExtension/withClipEntitlements.js @@ -0,0 +1,32 @@ +const {withInfoPlist} = require('@expo/config-plugins') +const plist = require('@expo/plist') +const path = require('path') +const fs = require('fs') + +const withClipEntitlements = (config, {targetName}) => { + // eslint-disable-next-line no-shadow + return withInfoPlist(config, config => { + const entitlementsPath = path.join( + config.modRequest.platformProjectRoot, + targetName, + `${targetName}.entitlements`, + ) + + const appClipEntitlements = { + 'com.apple.security.application-groups': [`group.app.bsky`], + 'com.apple.developer.parent-application-identifiers': [ + `$(AppIdentifierPrefix)${config.ios.bundleIdentifier}`, + ], + 'com.apple.developer.associated-domains': config.ios.associatedDomains, + } + + fs.mkdirSync(path.dirname(entitlementsPath), { + recursive: true, + }) + fs.writeFileSync(entitlementsPath, plist.default.build(appClipEntitlements)) + + return config + }) +} + +module.exports = {withClipEntitlements} diff --git a/plugins/starterPackAppClipExtension/withClipInfoPlist.js b/plugins/starterPackAppClipExtension/withClipInfoPlist.js new file mode 100644 index 0000000000..59fbed1a9e --- /dev/null +++ b/plugins/starterPackAppClipExtension/withClipInfoPlist.js @@ -0,0 +1,38 @@ +const {withInfoPlist} = require('@expo/config-plugins') +const plist = require('@expo/plist') +const path = require('path') +const fs = require('fs') + +const withClipInfoPlist = (config, {targetName}) => { + // eslint-disable-next-line no-shadow + return withInfoPlist(config, config => { + const targetPath = path.join( + config.modRequest.platformProjectRoot, + targetName, + 'Info.plist', + ) + + const newPlist = plist.default.build({ + NSAppClip: { + NSAppClipRequestEphemeralUserNotification: false, + NSAppClipRequestLocationConfirmation: false, + }, + UILaunchScreen: {}, + CFBundleName: '$(PRODUCT_NAME)', + CFBundleIdentifier: '$(PRODUCT_BUNDLE_IDENTIFIER)', + CFBundleVersion: '$(CURRENT_PROJECT_VERSION)', + CFBundleExecutable: '$(EXECUTABLE_NAME)', + CFBundlePackageType: '$(PRODUCT_BUNDLE_PACKAGE_TYPE)', + CFBundleShortVersionString: config.version, + CFBundleIconName: 'AppIcon', + UIViewControllerBasedStatusBarAppearance: 'NO', + }) + + fs.mkdirSync(path.dirname(targetPath), {recursive: true}) + fs.writeFileSync(targetPath, newPlist) + + return config + }) +} + +module.exports = {withClipInfoPlist} diff --git a/plugins/starterPackAppClipExtension/withFiles.js b/plugins/starterPackAppClipExtension/withFiles.js new file mode 100644 index 0000000000..ad99f5ae41 --- /dev/null +++ b/plugins/starterPackAppClipExtension/withFiles.js @@ -0,0 +1,40 @@ +const {withXcodeProject} = require('@expo/config-plugins') +const path = require('path') +const fs = require('fs') + +const FILES = ['AppDelegate.swift', 'ViewController.swift'] + +const withFiles = (config, {targetName}) => { + // eslint-disable-next-line no-shadow + return withXcodeProject(config, config => { + const basePath = path.join( + config.modRequest.projectRoot, + 'modules', + targetName, + ) + + for (const file of FILES) { + const sourcePath = path.join(basePath, file) + const targetPath = path.join( + config.modRequest.platformProjectRoot, + targetName, + file, + ) + + fs.mkdirSync(path.dirname(targetPath), {recursive: true}) + fs.copyFileSync(sourcePath, targetPath) + } + + const imagesBasePath = path.join(basePath, 'Images.xcassets') + const imagesTargetPath = path.join( + config.modRequest.platformProjectRoot, + targetName, + 'Images.xcassets', + ) + fs.cpSync(imagesBasePath, imagesTargetPath, {recursive: true}) + + return config + }) +} + +module.exports = {withFiles} diff --git a/plugins/starterPackAppClipExtension/withStarterPackAppClip.js b/plugins/starterPackAppClipExtension/withStarterPackAppClip.js new file mode 100644 index 0000000000..1e3f0b7029 --- /dev/null +++ b/plugins/starterPackAppClipExtension/withStarterPackAppClip.js @@ -0,0 +1,40 @@ +const {withPlugins} = require('@expo/config-plugins') +const {withAppEntitlements} = require('./withAppEntitlements') +const {withClipEntitlements} = require('./withClipEntitlements') +const {withClipInfoPlist} = require('./withClipInfoPlist') +const {withFiles} = require('./withFiles') +const {withXcodeTarget} = require('./withXcodeTarget') + +const APP_CLIP_TARGET_NAME = 'BlueskyClip' + +const withStarterPackAppClip = config => { + return withPlugins(config, [ + withAppEntitlements, + [ + withClipEntitlements, + { + targetName: APP_CLIP_TARGET_NAME, + }, + ], + [ + withClipInfoPlist, + { + targetName: APP_CLIP_TARGET_NAME, + }, + ], + [ + withFiles, + { + targetName: APP_CLIP_TARGET_NAME, + }, + ], + [ + withXcodeTarget, + { + targetName: APP_CLIP_TARGET_NAME, + }, + ], + ]) +} + +module.exports = withStarterPackAppClip diff --git a/plugins/starterPackAppClipExtension/withXcodeTarget.js b/plugins/starterPackAppClipExtension/withXcodeTarget.js new file mode 100644 index 0000000000..61d5f81b07 --- /dev/null +++ b/plugins/starterPackAppClipExtension/withXcodeTarget.js @@ -0,0 +1,91 @@ +const {withXcodeProject} = require('@expo/config-plugins') + +const BUILD_PHASE_FILES = ['AppDelegate.swift', 'ViewController.swift'] + +const withXcodeTarget = (config, {targetName}) => { + // eslint-disable-next-line no-shadow + return withXcodeProject(config, config => { + const pbxProject = config.modResults + + const target = pbxProject.addTarget(targetName, 'application', targetName) + target.pbxNativeTarget.productType = `"com.apple.product-type.application.on-demand-install-capable"` + pbxProject.addBuildPhase( + BUILD_PHASE_FILES.map(f => `${targetName}/${f}`), + 'PBXSourcesBuildPhase', + 'Sources', + target.uuid, + 'application', + '"AppClips"', + ) + pbxProject.addBuildPhase( + [`${targetName}/Images.xcassets`], + 'PBXResourcesBuildPhase', + 'Resources', + target.uuid, + 'application', + '"AppClips"', + ) + + const pbxGroup = pbxProject.addPbxGroup([ + 'AppDelegate.swift', + 'ViewController.swift', + 'Images.xcassets', + `${targetName}.entitlements`, + 'Info.plist', + ]) + + pbxProject.addFile(`${targetName}/Info.plist`, pbxGroup.uuid) + const configurations = pbxProject.pbxXCBuildConfigurationSection() + for (const key in configurations) { + if (typeof configurations[key].buildSettings !== 'undefined') { + const buildSettingsObj = configurations[key].buildSettings + if ( + typeof buildSettingsObj.PRODUCT_NAME !== 'undefined' && + buildSettingsObj.PRODUCT_NAME === `"${targetName}"` + ) { + buildSettingsObj.CLANG_ENABLE_MODULES = 'YES' + buildSettingsObj.INFOPLIST_FILE = `"${targetName}/Info.plist"` + buildSettingsObj.CODE_SIGN_ENTITLEMENTS = `"${targetName}/${targetName}.entitlements"` + buildSettingsObj.CODE_SIGN_STYLE = 'Automatic' + buildSettingsObj.CURRENT_PROJECT_VERSION = `"${ + process.env.BSKY_IOS_BUILD_NUMBER ?? '1' + }"` + buildSettingsObj.GENERATE_INFOPLIST_FILE = 'YES' + buildSettingsObj.MARKETING_VERSION = `"${config.version}"` + buildSettingsObj.PRODUCT_BUNDLE_IDENTIFIER = `"${config.ios?.bundleIdentifier}.AppClip"` + buildSettingsObj.SWIFT_EMIT_LOC_STRINGS = 'YES' + buildSettingsObj.SWIFT_VERSION = '5.0' + buildSettingsObj.TARGETED_DEVICE_FAMILY = `"1"` + buildSettingsObj.DEVELOPMENT_TEAM = 'B3LX46C5HS' + buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET = '14.0' + buildSettingsObj.ASSETCATALOG_COMPILER_APPICON_NAME = 'AppIcon' + } + } + } + + pbxProject.addTargetAttribute('DevelopmentTeam', 'B3LX46C5HS', targetName) + + if (!pbxProject.hash.project.objects.PBXTargetDependency) { + pbxProject.hash.project.objects.PBXTargetDependency = {} + } + if (!pbxProject.hash.project.objects.PBXContainerItemProxy) { + pbxProject.hash.project.objects.PBXContainerItemProxy = {} + } + pbxProject.addTargetDependency(pbxProject.getFirstTarget().uuid, [ + target.uuid, + ]) + + pbxProject.addBuildPhase( + [`${targetName}.app`], + 'PBXCopyFilesBuildPhase', + 'Embed App Clips', + pbxProject.getFirstTarget().uuid, + 'application', + '"AppClips"', + ) + + return config + }) +} + +module.exports = {withXcodeTarget} diff --git a/scripts/updateExtensions.sh b/scripts/updateExtensions.sh index f3e972aa7b..b01134eebe 100755 --- a/scripts/updateExtensions.sh +++ b/scripts/updateExtensions.sh @@ -1,6 +1,7 @@ #!/bin/bash IOS_SHARE_EXTENSION_DIRECTORY="./ios/Share-with-Bluesky" IOS_NOTIFICATION_EXTENSION_DIRECTORY="./ios/BlueskyNSE" +IOS_APP_CLIP_DIRECTORY="./ios/BlueskyClip" MODULES_DIRECTORY="./modules" if [ ! -d $IOS_SHARE_EXTENSION_DIRECTORY ]; then @@ -16,3 +17,11 @@ if [ ! -d $IOS_NOTIFICATION_EXTENSION_DIRECTORY ]; then else cp -R $IOS_NOTIFICATION_EXTENSION_DIRECTORY $MODULES_DIRECTORY fi + + +if [ ! -d $IOS_APP_CLIP_DIRECTORY ]; then + echo "$IOS_APP_CLIP_DIRECTORY not found inside of your iOS project." + exit 1 +else + cp -R $IOS_APP_CLIP_DIRECTORY $MODULES_DIRECTORY +fi diff --git a/src/App.native.tsx b/src/App.native.tsx index 4c73d87525..639276a12d 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -46,11 +46,13 @@ import {readLastActiveAccount} from '#/state/session/util' import {Provider as ShellStateProvider} from '#/state/shell' import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' +import {Provider as StarterPackProvider} from '#/state/shell/starter-pack' import {TestCtrls} from '#/view/com/testing/TestCtrls' import * as Toast from '#/view/com/util/Toast' import {Shell} from '#/view/shell' import {ThemeProvider as Alf} from '#/alf' import {useColorModeTheme} from '#/alf/util/useColorModeTheme' +import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry' import {Provider as PortalProvider} from '#/components/Portal' import {Splash} from '#/Splash' import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' @@ -67,6 +69,7 @@ function InnerApp() { const {_} = useLingui() useIntentHandler() + const hasCheckedReferrer = useStarterPackEntry() // init useEffect(() => { @@ -98,7 +101,7 @@ function InnerApp() { - + - + + + diff --git a/src/App.web.tsx b/src/App.web.tsx index 00939c9eb4..31a59d97dd 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -35,11 +35,13 @@ import {readLastActiveAccount} from '#/state/session/util' import {Provider as ShellStateProvider} from '#/state/shell' import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' +import {Provider as StarterPackProvider} from '#/state/shell/starter-pack' import * as Toast from '#/view/com/util/Toast' import {ToastContainer} from '#/view/com/util/Toast.web' import {Shell} from '#/view/shell/index' import {ThemeProvider as Alf} from '#/alf' import {useColorModeTheme} from '#/alf/util/useColorModeTheme' +import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry' import {Provider as PortalProvider} from '#/components/Portal' import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' import I18nProvider from './locale/i18nProvider' @@ -52,6 +54,7 @@ function InnerApp() { const theme = useColorModeTheme() const {_} = useLingui() useIntentHandler() + const hasCheckedReferrer = useStarterPackEntry() // init useEffect(() => { @@ -77,7 +80,7 @@ function InnerApp() { }, [_]) // wait for session to resume - if (!isReady) return null + if (!isReady || !hasCheckedReferrer) return null return ( @@ -146,7 +149,9 @@ function App() { - + + + diff --git a/src/Navigation.tsx b/src/Navigation.tsx index f2b7cd911f..5cb4f4105f 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -43,6 +43,8 @@ import HashtagScreen from '#/screens/Hashtag' import {ModerationScreen} from '#/screens/Moderation' import {ProfileKnownFollowersScreen} from '#/screens/Profile/KnownFollowers' import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' +import {StarterPackScreen} from '#/screens/StarterPack/StarterPackScreen' +import {Wizard} from '#/screens/StarterPack/Wizard' import {init as initAnalytics} from './lib/analytics/analytics' import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration' import {attachRouteToLogEvents, logEvent} from './lib/statsig/statsig' @@ -317,6 +319,21 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { getComponent={() => FeedsScreen} options={{title: title(msg`Feeds`)}} /> + StarterPackScreen} + options={{title: title(msg`Starter Pack`), requireAuth: true}} + /> + Wizard} + options={{title: title(msg`Create a starter pack`), requireAuth: true}} + /> + Wizard} + options={{title: title(msg`Edit your starter pack`), requireAuth: true}} + /> ) } @@ -371,6 +388,7 @@ function HomeTabNavigator() { contentStyle: pal.view, }}> HomeScreen} /> + HomeScreen} /> {commonScreens(HomeTab)} ) @@ -507,6 +525,11 @@ const FlatNavigator = () => { getComponent={() => MessagesScreen} options={{title: title(msg`Messages`), requireAuth: true}} /> + HomeScreen} + options={{title: title(msg`Home`)}} + /> {commonScreens(Flat as typeof HomeTab, numUnread)} ) diff --git a/src/components/LinearGradientBackground.tsx b/src/components/LinearGradientBackground.tsx new file mode 100644 index 0000000000..f516b19f5f --- /dev/null +++ b/src/components/LinearGradientBackground.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import {StyleProp, ViewStyle} from 'react-native' +import {LinearGradient} from 'expo-linear-gradient' + +import {gradients} from '#/alf/tokens' + +export function LinearGradientBackground({ + style, + children, +}: { + style: StyleProp + children: React.ReactNode +}) { + const gradient = gradients.sky.values.map(([_, color]) => { + return color + }) + + return ( + + {children} + + ) +} diff --git a/src/components/NewskieDialog.tsx b/src/components/NewskieDialog.tsx index 0354bfc432..6743a592ba 100644 --- a/src/components/NewskieDialog.tsx +++ b/src/components/NewskieDialog.tsx @@ -9,11 +9,13 @@ import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {HITSLOP_10} from 'lib/constants' import {sanitizeDisplayName} from 'lib/strings/display-names' -import {atoms as a} from '#/alf' -import {Button} from '#/components/Button' +import {isWeb} from 'platform/detection' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {useDialogControl} from '#/components/Dialog' import {Newskie} from '#/components/icons/Newskie' +import * as StarterPackCard from '#/components/StarterPack/StarterPackCard' import {Text} from '#/components/Typography' export function NewskieDialog({ @@ -24,6 +26,7 @@ export function NewskieDialog({ disabled?: boolean }) { const {_} = useLingui() + const t = useTheme() const moderationOpts = useModerationOpts() const control = useDialogControl() const profileName = React.useMemo(() => { @@ -68,15 +71,62 @@ export function NewskieDialog({ label={_(msg`New user info dialog`)} style={[{width: 'auto', maxWidth: 400, minWidth: 200}]}> - - Say hello! - - - - {profileName} joined Bluesky{' '} - {timeAgo(createdAt, now, {format: 'long'})} ago - + + + + Say hello! + + + + {profile.joinedViaStarterPack ? ( + + {profileName} joined Bluesky using a starter pack{' '} + {timeAgo(createdAt, now, {format: 'long'})} ago + + ) : ( + + {profileName} joined Bluesky{' '} + {timeAgo(createdAt, now, {format: 'long'})} ago + + )} + {profile.joinedViaStarterPack ? ( + { + control.close() + }}> + + + + + ) : null} + diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx new file mode 100644 index 0000000000..a0d222854b --- /dev/null +++ b/src/components/ProfileCard.tsx @@ -0,0 +1,91 @@ +import React from 'react' +import {View} from 'react-native' +import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' + +import {createSanitizedDisplayName} from 'lib/moderation/create-sanitized-display-name' +import {sanitizeHandle} from 'lib/strings/handles' +import {useProfileShadow} from 'state/cache/profile-shadow' +import {useSession} from 'state/session' +import {FollowButton} from 'view/com/profile/FollowButton' +import {ProfileCardPills} from 'view/com/profile/ProfileCard' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {atoms as a, useTheme} from '#/alf' +import {Link} from '#/components/Link' +import {Text} from '#/components/Typography' + +export function Default({ + profile: profileUnshadowed, + moderationOpts, + logContext = 'ProfileCard', +}: { + profile: AppBskyActorDefs.ProfileViewDetailed + moderationOpts: ModerationOpts + logContext?: 'ProfileCard' | 'StarterPackProfilesList' +}) { + const t = useTheme() + const {currentAccount, hasSession} = useSession() + + const profile = useProfileShadow(profileUnshadowed) + const name = createSanitizedDisplayName(profile) + const handle = `@${sanitizeHandle(profile.handle)}` + const moderation = moderateProfile(profile, moderationOpts) + + return ( + + + + + + {name} + + + {handle} + + + {hasSession && profile.did !== currentAccount?.did && ( + + + + )} + + + + + {profile.description && ( + + {profile.description} + + )} + + ) +} + +function Wrapper({did, children}: {did: string; children: React.ReactNode}) { + return ( + + {children} + + ) +} diff --git a/src/components/ReportDialog/SelectReportOptionView.tsx b/src/components/ReportDialog/SelectReportOptionView.tsx index 4413cbe890..169c07d732 100644 --- a/src/components/ReportDialog/SelectReportOptionView.tsx +++ b/src/components/ReportDialog/SelectReportOptionView.tsx @@ -55,6 +55,9 @@ export function SelectReportOptionView({ } else if (props.params.type === 'feedgen') { title = _(msg`Report this feed`) description = _(msg`Why should this feed be reviewed?`) + } else if (props.params.type === 'starterpack') { + title = _(msg`Report this starter pack`) + description = _(msg`Why should this starter pack be reviewed?`) } else if (props.params.type === 'convoMessage') { title = _(msg`Report this message`) description = _(msg`Why should this message be reviewed?`) diff --git a/src/components/ReportDialog/types.ts b/src/components/ReportDialog/types.ts index ceabe0b901..3f43db4a19 100644 --- a/src/components/ReportDialog/types.ts +++ b/src/components/ReportDialog/types.ts @@ -4,7 +4,7 @@ export type ReportDialogProps = { control: Dialog.DialogOuterProps['control'] params: | { - type: 'post' | 'list' | 'feedgen' | 'other' + type: 'post' | 'list' | 'feedgen' | 'starterpack' | 'other' uri: string cid: string } diff --git a/src/components/StarterPack/Main/FeedsList.tsx b/src/components/StarterPack/Main/FeedsList.tsx new file mode 100644 index 0000000000..e350a422cf --- /dev/null +++ b/src/components/StarterPack/Main/FeedsList.tsx @@ -0,0 +1,68 @@ +import React, {useCallback} from 'react' +import {ListRenderItemInfo, View} from 'react-native' +import {AppBskyFeedDefs} from '@atproto/api' +import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' + +import {useBottomBarOffset} from 'lib/hooks/useBottomBarOffset' +import {isNative, isWeb} from 'platform/detection' +import {List, ListRef} from 'view/com/util/List' +import {SectionRef} from '#/screens/Profile/Sections/types' +import {atoms as a, useTheme} from '#/alf' +import * as FeedCard from '#/components/FeedCard' + +function keyExtractor(item: AppBskyFeedDefs.GeneratorView) { + return item.uri +} + +interface ProfilesListProps { + feeds: AppBskyFeedDefs.GeneratorView[] + headerHeight: number + scrollElRef: ListRef +} + +export const FeedsList = React.forwardRef( + function FeedsListImpl({feeds, headerHeight, scrollElRef}, ref) { + const [initialHeaderHeight] = React.useState(headerHeight) + const bottomBarOffset = useBottomBarOffset(20) + const t = useTheme() + + const onScrollToTop = useCallback(() => { + scrollElRef.current?.scrollToOffset({ + animated: isNative, + offset: -headerHeight, + }) + }, [scrollElRef, headerHeight]) + + React.useImperativeHandle(ref, () => ({ + scrollToTop: onScrollToTop, + })) + + const renderItem = ({item, index}: ListRenderItemInfo) => { + return ( + + + + ) + } + + return ( + + } + showsVerticalScrollIndicator={false} + desktopFixedHeight={true} + /> + ) + }, +) diff --git a/src/components/StarterPack/Main/ProfilesList.tsx b/src/components/StarterPack/Main/ProfilesList.tsx new file mode 100644 index 0000000000..72d35fe2b2 --- /dev/null +++ b/src/components/StarterPack/Main/ProfilesList.tsx @@ -0,0 +1,119 @@ +import React, {useCallback} from 'react' +import {ListRenderItemInfo, View} from 'react-native' +import { + AppBskyActorDefs, + AppBskyGraphGetList, + AtUri, + ModerationOpts, +} from '@atproto/api' +import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query' + +import {useBottomBarOffset} from 'lib/hooks/useBottomBarOffset' +import {isNative, isWeb} from 'platform/detection' +import {useSession} from 'state/session' +import {List, ListRef} from 'view/com/util/List' +import {SectionRef} from '#/screens/Profile/Sections/types' +import {atoms as a, useTheme} from '#/alf' +import {Default as ProfileCard} from '#/components/ProfileCard' + +function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic, index: number) { + return `${item.did}-${index}` +} + +interface ProfilesListProps { + listUri: string + listMembersQuery: UseInfiniteQueryResult< + InfiniteData + > + moderationOpts: ModerationOpts + headerHeight: number + scrollElRef: ListRef +} + +export const ProfilesList = React.forwardRef( + function ProfilesListImpl( + {listUri, listMembersQuery, moderationOpts, headerHeight, scrollElRef}, + ref, + ) { + const t = useTheme() + const [initialHeaderHeight] = React.useState(headerHeight) + const bottomBarOffset = useBottomBarOffset(20) + const {currentAccount} = useSession() + + const [isPTRing, setIsPTRing] = React.useState(false) + + const {data, refetch} = listMembersQuery + + // The server returns these sorted by descending creation date, so we want to invert + const profiles = data?.pages + .flatMap(p => p.items.map(i => i.subject)) + .reverse() + const isOwn = new AtUri(listUri).host === currentAccount?.did + + const getSortedProfiles = () => { + if (!profiles) return + if (!isOwn) return profiles + + const myIndex = profiles.findIndex(p => p.did === currentAccount?.did) + return myIndex !== -1 + ? [ + profiles[myIndex], + ...profiles.slice(0, myIndex), + ...profiles.slice(myIndex + 1), + ] + : profiles + } + const onScrollToTop = useCallback(() => { + scrollElRef.current?.scrollToOffset({ + animated: isNative, + offset: -headerHeight, + }) + }, [scrollElRef, headerHeight]) + + React.useImperativeHandle(ref, () => ({ + scrollToTop: onScrollToTop, + })) + + const renderItem = ({ + item, + index, + }: ListRenderItemInfo) => { + return ( + + + + ) + } + + if (listMembersQuery) + return ( + + } + showsVerticalScrollIndicator={false} + desktopFixedHeight + refreshing={isPTRing} + onRefresh={async () => { + setIsPTRing(true) + await refetch() + setIsPTRing(false) + }} + /> + ) + }, +) diff --git a/src/components/StarterPack/ProfileStarterPacks.tsx b/src/components/StarterPack/ProfileStarterPacks.tsx new file mode 100644 index 0000000000..096f04f2dd --- /dev/null +++ b/src/components/StarterPack/ProfileStarterPacks.tsx @@ -0,0 +1,320 @@ +import React from 'react' +import { + findNodeHandle, + ListRenderItemInfo, + StyleProp, + View, + ViewStyle, +} from 'react-native' +import {AppBskyGraphDefs, AppBskyGraphGetActorStarterPacks} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' +import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query' + +import {logger} from '#/logger' +import {useGenerateStarterPackMutation} from 'lib/generate-starterpack' +import {useBottomBarOffset} from 'lib/hooks/useBottomBarOffset' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {NavigationProp} from 'lib/routes/types' +import {parseStarterPackUri} from 'lib/strings/starter-pack' +import {List, ListRef} from 'view/com/util/List' +import {Text} from 'view/com/util/text/Text' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {useDialogControl} from '#/components/Dialog' +import {LinearGradientBackground} from '#/components/LinearGradientBackground' +import {Loader} from '#/components/Loader' +import * as Prompt from '#/components/Prompt' +import {Default as StarterPackCard} from '#/components/StarterPack/StarterPackCard' +import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '../icons/Plus' + +interface SectionRef { + scrollToTop: () => void +} + +interface ProfileFeedgensProps { + starterPacksQuery: UseInfiniteQueryResult< + InfiniteData, + Error + > + scrollElRef: ListRef + headerOffset: number + enabled?: boolean + style?: StyleProp + testID?: string + setScrollViewTag: (tag: number | null) => void + isMe: boolean +} + +function keyExtractor(item: AppBskyGraphDefs.StarterPackView) { + return item.uri +} + +export const ProfileStarterPacks = React.forwardRef< + SectionRef, + ProfileFeedgensProps +>(function ProfileFeedgensImpl( + { + starterPacksQuery: query, + scrollElRef, + headerOffset, + enabled, + style, + testID, + setScrollViewTag, + isMe, + }, + ref, +) { + const t = useTheme() + const bottomBarOffset = useBottomBarOffset(100) + const [isPTRing, setIsPTRing] = React.useState(false) + const {data, refetch, isFetching, hasNextPage, fetchNextPage} = query + const {isTabletOrDesktop} = useWebMediaQueries() + + const items = data?.pages.flatMap(page => page.starterPacks) + + React.useImperativeHandle(ref, () => ({ + scrollToTop: () => {}, + })) + + const onRefresh = React.useCallback(async () => { + setIsPTRing(true) + try { + await refetch() + } catch (err) { + logger.error('Failed to refresh starter packs', {message: err}) + } + setIsPTRing(false) + }, [refetch, setIsPTRing]) + + const onEndReached = React.useCallback(async () => { + if (isFetching || !hasNextPage) return + + try { + await fetchNextPage() + } catch (err) { + logger.error('Failed to load more starter packs', {message: err}) + } + }, [isFetching, hasNextPage, fetchNextPage]) + + React.useEffect(() => { + if (enabled && scrollElRef.current) { + const nativeTag = findNodeHandle(scrollElRef.current) + setScrollViewTag(nativeTag) + } + }, [enabled, scrollElRef, setScrollViewTag]) + + const renderItem = ({ + item, + index, + }: ListRenderItemInfo) => { + return ( + + + + ) + } + + return ( + + + + ) +}) + +function CreateAnother() { + const {_} = useLingui() + const t = useTheme() + const navigation = useNavigation() + + return ( + + + + ) +} + +function Empty() { + const {_} = useLingui() + const t = useTheme() + const navigation = useNavigation() + const confirmDialogControl = useDialogControl() + const followersDialogControl = useDialogControl() + const errorDialogControl = useDialogControl() + + const [isGenerating, setIsGenerating] = React.useState(false) + + const {mutate: generateStarterPack} = useGenerateStarterPackMutation({ + onSuccess: ({uri}) => { + const parsed = parseStarterPackUri(uri) + if (parsed) { + navigation.push('StarterPack', { + name: parsed.name, + rkey: parsed.rkey, + }) + } + setIsGenerating(false) + }, + onError: e => { + logger.error('Failed to generate starter pack', {safeMessage: e}) + setIsGenerating(false) + if (e.name === 'NOT_ENOUGH_FOLLOWERS') { + followersDialogControl.open() + } else { + errorDialogControl.open() + } + }, + }) + + const generate = () => { + setIsGenerating(true) + generateStarterPack() + } + + return ( + + + + You haven't created a starter pack yet! + + + Starter packs let you easily share your favorite feeds and people with + your friends. + + + + + + + + + + Generate a starter pack + + + + Bluesky will choose a set of recommended accounts from people in + your network. + + + + + { + navigation.navigate('StarterPackWizard') + }} + /> + + + {}} + showCancel={false} + /> + + + ) +} diff --git a/src/components/StarterPack/QrCode.tsx b/src/components/StarterPack/QrCode.tsx new file mode 100644 index 0000000000..08ee03d623 --- /dev/null +++ b/src/components/StarterPack/QrCode.tsx @@ -0,0 +1,119 @@ +import React from 'react' +import {View} from 'react-native' +import QRCode from 'react-native-qrcode-styled' +import ViewShot from 'react-native-view-shot' +import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' +import {Trans} from '@lingui/macro' + +import {isWeb} from 'platform/detection' +import {Logo} from 'view/icons/Logo' +import {Logotype} from 'view/icons/Logotype' +import {useTheme} from '#/alf' +import {atoms as a} from '#/alf' +import {LinearGradientBackground} from '#/components/LinearGradientBackground' +import {Text} from '#/components/Typography' + +interface Props { + starterPack: AppBskyGraphDefs.StarterPackView + link: string +} + +export const QrCode = React.forwardRef(function QrCode( + {starterPack, link}, + ref, +) { + const {record} = starterPack + + if (!AppBskyGraphStarterpack.isRecord(record)) { + return null + } + + return ( + + + + + {record.name} + + + + + Join the conversation + + + + + + + + on + + + + + + + + + + ) +}) + +export function QrCodeInner({link}: {link: string}) { + const t = useTheme() + + return ( + + ) +} diff --git a/src/components/StarterPack/QrCodeDialog.tsx b/src/components/StarterPack/QrCodeDialog.tsx new file mode 100644 index 0000000000..580c6cc7c8 --- /dev/null +++ b/src/components/StarterPack/QrCodeDialog.tsx @@ -0,0 +1,201 @@ +import React from 'react' +import {View} from 'react-native' +import ViewShot from 'react-native-view-shot' +import * as FS from 'expo-file-system' +import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' +import * as Sharing from 'expo-sharing' +import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {nanoid} from 'nanoid/non-secure' + +import {logger} from '#/logger' +import {saveImageToMediaLibrary} from 'lib/media/manip' +import {logEvent} from 'lib/statsig/statsig' +import {isNative, isWeb} from 'platform/detection' +import * as Toast from '#/view/com/util/Toast' +import {atoms as a} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {DialogControlProps} from '#/components/Dialog' +import {Loader} from '#/components/Loader' +import {QrCode} from '#/components/StarterPack/QrCode' + +export function QrCodeDialog({ + starterPack, + link, + control, +}: { + starterPack: AppBskyGraphDefs.StarterPackView + link?: string + control: DialogControlProps +}) { + const {_} = useLingui() + const [isProcessing, setIsProcessing] = React.useState(false) + + const ref = React.useRef(null) + + const getCanvas = (base64: string): Promise => { + return new Promise(resolve => { + const image = new Image() + image.onload = () => { + const canvas = document.createElement('canvas') + canvas.width = image.width + canvas.height = image.height + + const ctx = canvas.getContext('2d') + ctx?.drawImage(image, 0, 0) + resolve(canvas) + } + image.src = base64 + }) + } + + const onSavePress = async () => { + ref.current?.capture?.().then(async (uri: string) => { + if (isNative) { + const res = await requestMediaLibraryPermissionsAsync() + + if (!res) { + Toast.show( + _( + msg`You must grant access to your photo library to save a QR code`, + ), + ) + return + } + + const filename = `${FS.documentDirectory}/${nanoid(12)}.png` + + // Incase of a FS failure, don't crash the app + try { + await FS.copyAsync({from: uri, to: filename}) + await saveImageToMediaLibrary({uri: filename}) + await FS.deleteAsync(filename) + } catch (e: unknown) { + Toast.show(_(msg`An error occurred while saving the QR code!`)) + logger.error('Failed to save QR code', {error: e}) + return + } + } else { + setIsProcessing(true) + + if (!AppBskyGraphStarterpack.isRecord(starterPack.record)) { + return + } + + const canvas = await getCanvas(uri) + const imgHref = canvas + .toDataURL('image/png') + .replace('image/png', 'image/octet-stream') + + const link = document.createElement('a') + link.setAttribute( + 'download', + `${starterPack.record.name.replaceAll(' ', '_')}_Share_Card.png`, + ) + link.setAttribute('href', imgHref) + link.click() + } + + logEvent('starterPack:share', { + starterPack: starterPack.uri, + shareType: 'qrcode', + qrShareType: 'save', + }) + setIsProcessing(false) + Toast.show( + isWeb + ? _(msg`QR code has been downloaded!`) + : _(msg`QR code saved to your camera roll!`), + ) + control.close() + }) + } + + const onCopyPress = async () => { + setIsProcessing(true) + ref.current?.capture?.().then(async (uri: string) => { + const canvas = await getCanvas(uri) + // @ts-expect-error web only + canvas.toBlob((blob: Blob) => { + const item = new ClipboardItem({'image/png': blob}) + navigator.clipboard.write([item]) + }) + + logEvent('starterPack:share', { + starterPack: starterPack.uri, + shareType: 'qrcode', + qrShareType: 'copy', + }) + Toast.show(_(msg`QR code copied to your clipboard!`)) + setIsProcessing(false) + control.close() + }) + } + + const onSharePress = async () => { + ref.current?.capture?.().then(async (uri: string) => { + control.close(() => { + Sharing.shareAsync(uri, {mimeType: 'image/png', UTI: 'image/png'}).then( + () => { + logEvent('starterPack:share', { + starterPack: starterPack.uri, + shareType: 'qrcode', + qrShareType: 'share', + }) + }, + ) + }) + }) + } + + return ( + + + + + {!link ? ( + + + + ) : ( + <> + + {isProcessing ? ( + + + + ) : ( + + + + + )} + + )} + + + + ) +} diff --git a/src/components/StarterPack/ShareDialog.tsx b/src/components/StarterPack/ShareDialog.tsx new file mode 100644 index 0000000000..23fa10fb39 --- /dev/null +++ b/src/components/StarterPack/ShareDialog.tsx @@ -0,0 +1,180 @@ +import React from 'react' +import {View} from 'react-native' +import * as FS from 'expo-file-system' +import {Image} from 'expo-image' +import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' +import {AppBskyGraphDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {nanoid} from 'nanoid/non-secure' + +import {logger} from '#/logger' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {saveImageToMediaLibrary} from 'lib/media/manip' +import {shareUrl} from 'lib/sharing' +import {logEvent} from 'lib/statsig/statsig' +import {getStarterPackOgCard} from 'lib/strings/starter-pack' +import {isNative, isWeb} from 'platform/detection' +import * as Toast from 'view/com/util/Toast' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import {DialogControlProps} from '#/components/Dialog' +import * as Dialog from '#/components/Dialog' +import {Loader} from '#/components/Loader' +import {Text} from '#/components/Typography' + +interface Props { + starterPack: AppBskyGraphDefs.StarterPackView + link?: string + imageLoaded?: boolean + qrDialogControl: DialogControlProps + control: DialogControlProps +} + +export function ShareDialog(props: Props) { + return ( + + + + ) +} + +function ShareDialogInner({ + starterPack, + link, + imageLoaded, + qrDialogControl, + control, +}: Props) { + const {_} = useLingui() + const t = useTheme() + const {isTabletOrDesktop} = useWebMediaQueries() + + const imageUrl = getStarterPackOgCard(starterPack) + + const onShareLink = async () => { + if (!link) return + shareUrl(link) + logEvent('starterPack:share', { + starterPack: starterPack.uri, + shareType: 'link', + }) + control.close() + } + + const onSave = async () => { + const res = await requestMediaLibraryPermissionsAsync() + + if (!res) { + Toast.show( + _(msg`You must grant access to your photo library to save the image.`), + ) + return + } + + const cachePath = await Image.getCachePathAsync(imageUrl) + const filename = `${FS.documentDirectory}/${nanoid(12)}.png` + + if (!cachePath) { + Toast.show(_(msg`An error occurred while saving the image.`)) + return + } + + try { + await FS.copyAsync({from: cachePath, to: filename}) + await saveImageToMediaLibrary({uri: filename}) + await FS.deleteAsync(filename) + + Toast.show(_(msg`Image saved to your camera roll!`)) + control.close() + } catch (e: unknown) { + Toast.show(_(msg`An error occurred while saving the QR code!`)) + logger.error('Failed to save QR code', {error: e}) + return + } + } + + return ( + <> + + + {!imageLoaded || !link ? ( + + + + ) : ( + + + + Invite people to this starter pack! + + + + Share this starter pack and help people join your community on + Bluesky. + + + + + + + + {isNative && ( + + )} + + + )} + + + ) +} diff --git a/src/components/StarterPack/StarterPackCard.tsx b/src/components/StarterPack/StarterPackCard.tsx new file mode 100644 index 0000000000..ab904d7ffa --- /dev/null +++ b/src/components/StarterPack/StarterPackCard.tsx @@ -0,0 +1,117 @@ +import React from 'react' +import {View} from 'react-native' +import {AppBskyGraphStarterpack, AtUri} from '@atproto/api' +import {StarterPackViewBasic} from '@atproto/api/dist/client/types/app/bsky/graph/defs' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {sanitizeHandle} from 'lib/strings/handles' +import {useSession} from 'state/session' +import {atoms as a, useTheme} from '#/alf' +import {StarterPack} from '#/components/icons/StarterPack' +import {Link as InternalLink, LinkProps} from '#/components/Link' +import {Text} from '#/components/Typography' + +export function Default({starterPack}: {starterPack?: StarterPackViewBasic}) { + if (!starterPack) return null + return ( + + + + ) +} + +export function Notification({ + starterPack, +}: { + starterPack?: StarterPackViewBasic +}) { + if (!starterPack) return null + return ( + + + + ) +} + +export function Card({ + starterPack, + noIcon, + noDescription, +}: { + starterPack: StarterPackViewBasic + noIcon?: boolean + noDescription?: boolean +}) { + const {record, creator, joinedAllTimeCount} = starterPack + + const {_} = useLingui() + const t = useTheme() + const {currentAccount} = useSession() + + if (!AppBskyGraphStarterpack.isRecord(record)) { + return null + } + + return ( + + + {!noIcon ? : null} + + + {record.name} + + + + Starter pack by{' '} + {creator?.did === currentAccount?.did + ? _(msg`you`) + : `@${sanitizeHandle(creator.handle)}`} + + + + + {!noDescription && record.description ? ( + + {record.description} + + ) : null} + {!!joinedAllTimeCount && joinedAllTimeCount >= 50 && ( + + {joinedAllTimeCount} users have joined! + + )} + + ) +} + +export function Link({ + starterPack, + children, + ...rest +}: { + starterPack: StarterPackViewBasic +} & Omit) { + const {record} = starterPack + const {rkey, handleOrDid} = React.useMemo(() => { + const rkey = new AtUri(starterPack.uri).rkey + const {creator} = starterPack + return {rkey, handleOrDid: creator.handle || creator.did} + }, [starterPack]) + + if (!AppBskyGraphStarterpack.isRecord(record)) { + return null + } + + return ( + + {children} + + ) +} diff --git a/src/components/StarterPack/Wizard/ScreenTransition.tsx b/src/components/StarterPack/Wizard/ScreenTransition.tsx new file mode 100644 index 0000000000..b7cd4e4c1f --- /dev/null +++ b/src/components/StarterPack/Wizard/ScreenTransition.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import {StyleProp, ViewStyle} from 'react-native' +import Animated, { + FadeIn, + FadeOut, + SlideInLeft, + SlideInRight, +} from 'react-native-reanimated' + +import {isWeb} from 'platform/detection' + +export function ScreenTransition({ + direction, + style, + children, +}: { + direction: 'Backward' | 'Forward' + style?: StyleProp + children: React.ReactNode +}) { + const entering = direction === 'Forward' ? SlideInRight : SlideInLeft + + return ( + + {children} + + ) +} diff --git a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx new file mode 100644 index 0000000000..bf250ac354 --- /dev/null +++ b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx @@ -0,0 +1,152 @@ +import React, {useRef} from 'react' +import type {ListRenderItemInfo} from 'react-native' +import {View} from 'react-native' +import {AppBskyActorDefs, ModerationOpts} from '@atproto/api' +import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' +import {BottomSheetFlatListMethods} from '@discord/bottom-sheet' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {isWeb} from 'platform/detection' +import {useSession} from 'state/session' +import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State' +import {atoms as a, native, useTheme, web} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import { + WizardFeedCard, + WizardProfileCard, +} from '#/components/StarterPack/Wizard/WizardListCard' +import {Text} from '#/components/Typography' + +function keyExtractor( + item: AppBskyActorDefs.ProfileViewBasic | GeneratorView, + index: number, +) { + return `${item.did}-${index}` +} + +export function WizardEditListDialog({ + control, + state, + dispatch, + moderationOpts, + profile, +}: { + control: Dialog.DialogControlProps + state: WizardState + dispatch: (action: WizardAction) => void + moderationOpts: ModerationOpts + profile: AppBskyActorDefs.ProfileViewBasic +}) { + const {_} = useLingui() + const t = useTheme() + const {currentAccount} = useSession() + + const listRef = useRef(null) + + const getData = () => { + if (state.currentStep === 'Feeds') return state.feeds + + return [ + profile, + ...state.profiles.filter(p => p.did !== currentAccount?.did), + ] + } + + const renderItem = ({item}: ListRenderItemInfo) => + state.currentStep === 'Profiles' ? ( + + ) : ( + + ) + + return ( + + + + + + {state.currentStep === 'Profiles' ? ( + Edit People + ) : ( + Edit Feeds + )} + + + {isWeb && ( + + )} + + + } + stickyHeaderIndices={[0]} + style={[ + web([a.py_0, {height: '100vh', maxHeight: 600}, a.px_0]), + native({ + height: '100%', + paddingHorizontal: 0, + marginTop: 0, + paddingTop: 0, + borderTopLeftRadius: 40, + borderTopRightRadius: 40, + }), + ]} + webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]} + keyboardDismissMode="on-drag" + removeClippedSubviews={true} + /> + + ) +} diff --git a/src/components/StarterPack/Wizard/WizardListCard.tsx b/src/components/StarterPack/Wizard/WizardListCard.tsx new file mode 100644 index 0000000000..f1332011d9 --- /dev/null +++ b/src/components/StarterPack/Wizard/WizardListCard.tsx @@ -0,0 +1,182 @@ +import React from 'react' +import {Keyboard, View} from 'react-native' +import { + AppBskyActorDefs, + AppBskyFeedDefs, + moderateFeedGenerator, + moderateProfile, + ModerationOpts, + ModerationUI, +} from '@atproto/api' +import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {DISCOVER_FEED_URI} from 'lib/constants' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {sanitizeHandle} from 'lib/strings/handles' +import {useSession} from 'state/session' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State' +import {atoms as a, useTheme} from '#/alf' +import * as Toggle from '#/components/forms/Toggle' +import {Checkbox} from '#/components/forms/Toggle' +import {Text} from '#/components/Typography' + +function WizardListCard({ + type, + displayName, + subtitle, + onPress, + avatar, + included, + disabled, + moderationUi, +}: { + type: 'user' | 'algo' + profile?: AppBskyActorDefs.ProfileViewBasic + feed?: AppBskyFeedDefs.GeneratorView + displayName: string + subtitle: string + onPress: () => void + avatar?: string + included?: boolean + disabled?: boolean + moderationUi: ModerationUI +}) { + const t = useTheme() + const {_} = useLingui() + + return ( + + + + + {displayName} + + + {subtitle} + + + + + ) +} + +export function WizardProfileCard({ + state, + dispatch, + profile, + moderationOpts, +}: { + state: WizardState + dispatch: (action: WizardAction) => void + profile: AppBskyActorDefs.ProfileViewBasic + moderationOpts: ModerationOpts +}) { + const {currentAccount} = useSession() + + const isMe = profile.did === currentAccount?.did + const included = isMe || state.profiles.some(p => p.did === profile.did) + const disabled = isMe || (!included && state.profiles.length >= 49) + const moderationUi = moderateProfile(profile, moderationOpts).ui('avatar') + const displayName = profile.displayName + ? sanitizeDisplayName(profile.displayName) + : `@${sanitizeHandle(profile.handle)}` + + const onPress = () => { + if (disabled) return + + Keyboard.dismiss() + if (profile.did === currentAccount?.did) return + + if (!included) { + dispatch({type: 'AddProfile', profile}) + } else { + dispatch({type: 'RemoveProfile', profileDid: profile.did}) + } + } + + return ( + + ) +} + +export function WizardFeedCard({ + generator, + state, + dispatch, + moderationOpts, +}: { + generator: GeneratorView + state: WizardState + dispatch: (action: WizardAction) => void + moderationOpts: ModerationOpts +}) { + const isDiscover = generator.uri === DISCOVER_FEED_URI + const included = isDiscover || state.feeds.some(f => f.uri === generator.uri) + const disabled = isDiscover || (!included && state.feeds.length >= 3) + const moderationUi = moderateFeedGenerator(generator, moderationOpts).ui( + 'avatar', + ) + + const onPress = () => { + if (disabled) return + + Keyboard.dismiss() + if (included) { + dispatch({type: 'RemoveFeed', feedUri: generator.uri}) + } else { + dispatch({type: 'AddFeed', feed: generator}) + } + } + + return ( + + ) +} diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx index f7a827b493..d513a6db99 100644 --- a/src/components/forms/TextField.tsx +++ b/src/components/forms/TextField.tsx @@ -140,6 +140,7 @@ export function createInput(Component: typeof TextInput) { onChangeText, isInvalid, inputRef, + style, ...rest }: InputProps) { const t = useTheme() @@ -206,6 +207,7 @@ export function createInput(Component: typeof TextInput) { android({ paddingBottom: 16, }), + style, ]} /> diff --git a/src/components/hooks/useStarterPackEntry.native.ts b/src/components/hooks/useStarterPackEntry.native.ts new file mode 100644 index 0000000000..b6e4ab05b1 --- /dev/null +++ b/src/components/hooks/useStarterPackEntry.native.ts @@ -0,0 +1,68 @@ +import React from 'react' + +import { + createStarterPackLinkFromAndroidReferrer, + httpStarterPackUriToAtUri, +} from 'lib/strings/starter-pack' +import {isAndroid} from 'platform/detection' +import {useHasCheckedForStarterPack} from 'state/preferences/used-starter-packs' +import {useSetActiveStarterPack} from 'state/shell/starter-pack' +import {DevicePrefs, Referrer} from '../../../modules/expo-bluesky-swiss-army' + +export function useStarterPackEntry() { + const [ready, setReady] = React.useState(false) + const setActiveStarterPack = useSetActiveStarterPack() + const hasCheckedForStarterPack = useHasCheckedForStarterPack() + + React.useEffect(() => { + if (ready) return + + // On Android, we cannot clear the referral link. It gets stored for 90 days and all we can do is query for it. So, + // let's just ensure we never check again after the first time. + if (hasCheckedForStarterPack) { + setReady(true) + return + } + + // Safety for Android. Very unlike this could happen, but just in case. The response should be nearly immediate + const timeout = setTimeout(() => { + setReady(true) + }, 500) + + ;(async () => { + let uri: string | null | undefined + + if (isAndroid) { + const res = await Referrer.getGooglePlayReferrerInfoAsync() + + if (res && res.installReferrer) { + uri = createStarterPackLinkFromAndroidReferrer(res.installReferrer) + } + } else { + const res = await DevicePrefs.getStringValueAsync( + 'starterPackUri', + true, + ) + + if (res) { + uri = httpStarterPackUriToAtUri(res) + DevicePrefs.setStringValueAsync('starterPackUri', null, true) + } + } + + if (uri) { + setActiveStarterPack({ + uri, + }) + } + + setReady(true) + })() + + return () => { + clearTimeout(timeout) + } + }, [ready, setActiveStarterPack, hasCheckedForStarterPack]) + + return ready +} diff --git a/src/components/hooks/useStarterPackEntry.ts b/src/components/hooks/useStarterPackEntry.ts new file mode 100644 index 0000000000..dba801e093 --- /dev/null +++ b/src/components/hooks/useStarterPackEntry.ts @@ -0,0 +1,29 @@ +import React from 'react' + +import {httpStarterPackUriToAtUri} from 'lib/strings/starter-pack' +import {useSetActiveStarterPack} from 'state/shell/starter-pack' + +export function useStarterPackEntry() { + const [ready, setReady] = React.useState(false) + + const setActiveStarterPack = useSetActiveStarterPack() + + React.useEffect(() => { + const href = window.location.href + const atUri = httpStarterPackUriToAtUri(href) + + if (atUri) { + const url = new URL(href) + // Determines if an App Clip is loading this landing page + const isClip = url.searchParams.get('clip') === 'true' + setActiveStarterPack({ + uri: atUri, + isClip, + }) + } + + setReady(true) + }, [setActiveStarterPack]) + + return ready +} diff --git a/src/components/icons/QrCode.tsx b/src/components/icons/QrCode.tsx new file mode 100644 index 0000000000..e841071f70 --- /dev/null +++ b/src/components/icons/QrCode.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const QrCode_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M3 5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5Zm6 0H5v4h4V5ZM3 15a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4Zm6 0H5v4h4v-4ZM13 5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2V5Zm6 0h-4v4h4V5ZM14 13a1 1 0 0 1 1 1v1h1a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1Zm3 1a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Zm0 4a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-1v1a1 1 0 1 1-2 0v-2Z', +}) diff --git a/src/components/icons/StarterPack.tsx b/src/components/icons/StarterPack.tsx new file mode 100644 index 0000000000..8c678bca47 --- /dev/null +++ b/src/components/icons/StarterPack.tsx @@ -0,0 +1,8 @@ +import {createMultiPathSVG} from './TEMPLATE' + +export const StarterPack = createMultiPathSVG({ + paths: [ + 'M11.26 5.227 5.02 6.899c-.734.197-1.17.95-.973 1.685l1.672 6.24c.197.734.951 1.17 1.685.973l6.24-1.672c.734-.197 1.17-.951.973-1.685L12.945 6.2a1.375 1.375 0 0 0-1.685-.973Zm-6.566.459a2.632 2.632 0 0 0-1.86 3.223l1.672 6.24a2.632 2.632 0 0 0 3.223 1.861l6.24-1.672a2.631 2.631 0 0 0 1.861-3.223l-1.672-6.24a2.632 2.632 0 0 0-3.223-1.861l-6.24 1.672Z', + 'M15.138 18.411a4.606 4.606 0 1 0 0-9.211 4.606 4.606 0 0 0 0 9.211Zm0 1.257a5.862 5.862 0 1 0 0-11.724 5.862 5.862 0 0 0 0 11.724Z', + ], +}) diff --git a/src/components/icons/TEMPLATE.tsx b/src/components/icons/TEMPLATE.tsx index f49c4280bb..47a5c36b2a 100644 --- a/src/components/icons/TEMPLATE.tsx +++ b/src/components/icons/TEMPLATE.tsx @@ -30,7 +30,7 @@ export const IconTemplate_Stroke2_Corner0_Rounded = React.forwardRef( export function createSinglePathSVG({path}: {path: string}) { return React.forwardRef(function LogoImpl(props, ref) { - const {fill, size, style, ...rest} = useCommonSVGProps(props) + const {fill, size, style, gradient, ...rest} = useCommonSVGProps(props) return ( + {gradient} ) }) } + +export function createMultiPathSVG({paths}: {paths: string[]}) { + return React.forwardRef(function LogoImpl(props, ref) { + const {fill, size, style, gradient, ...rest} = useCommonSVGProps(props) + + return ( + + {gradient} + {paths.map((path, i) => ( + + ))} + + ) + }) +} diff --git a/src/components/icons/common.ts b/src/components/icons/common.ts deleted file mode 100644 index 669c157f51..0000000000 --- a/src/components/icons/common.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {StyleSheet, TextProps} from 'react-native' -import type {PathProps, SvgProps} from 'react-native-svg' - -import {tokens} from '#/alf' - -export type Props = { - fill?: PathProps['fill'] - style?: TextProps['style'] - size?: keyof typeof sizes -} & Omit - -export const sizes = { - xs: 12, - sm: 16, - md: 20, - lg: 24, - xl: 28, -} - -export function useCommonSVGProps(props: Props) { - const {fill, size, ...rest} = props - const style = StyleSheet.flatten(rest.style) - const _fill = fill || style?.color || tokens.color.blue_500 - const _size = Number(size ? sizes[size] : rest.width || sizes.md) - - return { - fill: _fill, - size: _size, - style, - ...rest, - } -} diff --git a/src/components/icons/common.tsx b/src/components/icons/common.tsx new file mode 100644 index 0000000000..662718338c --- /dev/null +++ b/src/components/icons/common.tsx @@ -0,0 +1,59 @@ +import React from 'react' +import {StyleSheet, TextProps} from 'react-native' +import type {PathProps, SvgProps} from 'react-native-svg' +import {Defs, LinearGradient, Stop} from 'react-native-svg' +import {nanoid} from 'nanoid/non-secure' + +import {tokens} from '#/alf' + +export type Props = { + fill?: PathProps['fill'] + style?: TextProps['style'] + size?: keyof typeof sizes + gradient?: keyof typeof tokens.gradients +} & Omit + +export const sizes = { + xs: 12, + sm: 16, + md: 20, + lg: 24, + xl: 28, +} + +export function useCommonSVGProps(props: Props) { + const {fill, size, gradient, ...rest} = props + const style = StyleSheet.flatten(rest.style) + const _size = Number(size ? sizes[size] : rest.width || sizes.md) + let _fill = fill || style?.color || tokens.color.blue_500 + let gradientDef = null + + if (gradient && tokens.gradients[gradient]) { + const id = gradient + '_' + nanoid() + const config = tokens.gradients[gradient] + _fill = `url(#${id})` + gradientDef = ( + + + {config.values.map(([stop, fill]) => ( + + ))} + + + ) + } + + return { + fill: _fill, + size: _size, + style, + gradient: gradientDef, + ...rest, + } +} diff --git a/src/lib/browser.native.ts b/src/lib/browser.native.ts index fb9be56f10..8e045138c8 100644 --- a/src/lib/browser.native.ts +++ b/src/lib/browser.native.ts @@ -1,3 +1,4 @@ export const isSafari = false export const isFirefox = false export const isTouchDevice = true +export const isAndroidWeb = false diff --git a/src/lib/browser.ts b/src/lib/browser.ts index d178a9a64e..08c43fbfdf 100644 --- a/src/lib/browser.ts +++ b/src/lib/browser.ts @@ -5,3 +5,5 @@ export const isSafari = /^((?!chrome|android).)*safari/i.test( export const isFirefox = /firefox|fxios/i.test(navigator.userAgent) export const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 1 +export const isAndroidWeb = + /android/i.test(navigator.userAgent) && isTouchDevice diff --git a/src/lib/generate-starterpack.ts b/src/lib/generate-starterpack.ts new file mode 100644 index 0000000000..64d30a954f --- /dev/null +++ b/src/lib/generate-starterpack.ts @@ -0,0 +1,164 @@ +import { + AppBskyActorDefs, + AppBskyGraphGetStarterPack, + BskyAgent, + Facet, +} from '@atproto/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useMutation} from '@tanstack/react-query' + +import {until} from 'lib/async/until' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {sanitizeHandle} from 'lib/strings/handles' +import {enforceLen} from 'lib/strings/helpers' +import {useAgent} from 'state/session' + +export const createStarterPackList = async ({ + name, + description, + descriptionFacets, + profiles, + agent, +}: { + name: string + description?: string + descriptionFacets?: Facet[] + profiles: AppBskyActorDefs.ProfileViewBasic[] + agent: BskyAgent +}): Promise<{uri: string; cid: string}> => { + if (profiles.length === 0) throw new Error('No profiles given') + + const list = await agent.app.bsky.graph.list.create( + {repo: agent.session!.did}, + { + name, + description, + descriptionFacets, + avatar: undefined, + createdAt: new Date().toISOString(), + purpose: 'app.bsky.graph.defs#referencelist', + }, + ) + if (!list) throw new Error('List creation failed') + await agent.com.atproto.repo.applyWrites({ + repo: agent.session!.did, + writes: [ + createListItem({did: agent.session!.did, listUri: list.uri}), + ].concat( + profiles + // Ensure we don't have ourselves in this list twice + .filter(p => p.did !== agent.session!.did) + .map(p => createListItem({did: p.did, listUri: list.uri})), + ), + }) + + return list +} + +export function useGenerateStarterPackMutation({ + onSuccess, + onError, +}: { + onSuccess: ({uri, cid}: {uri: string; cid: string}) => void + onError: (e: Error) => void +}) { + const {_} = useLingui() + const agent = useAgent() + const starterPackString = _(msg`Starter Pack`) + + return useMutation<{uri: string; cid: string}, Error, void>({ + mutationFn: async () => { + let profile: AppBskyActorDefs.ProfileViewBasic | undefined + let profiles: AppBskyActorDefs.ProfileViewBasic[] | undefined + + await Promise.all([ + (async () => { + profile = ( + await agent.app.bsky.actor.getProfile({ + actor: agent.session!.did, + }) + ).data + })(), + (async () => { + profiles = ( + await agent.app.bsky.actor.searchActors({ + q: encodeURIComponent('*'), + limit: 49, + }) + ).data.actors.filter(p => p.viewer?.following) + })(), + ]) + + if (!profile || !profiles) { + throw new Error('ERROR_DATA') + } + + // We include ourselves when we make the list + if (profiles.length < 7) { + throw new Error('NOT_ENOUGH_FOLLOWERS') + } + + const displayName = enforceLen( + profile.displayName + ? sanitizeDisplayName(profile.displayName) + : `@${sanitizeHandle(profile.handle)}`, + 25, + true, + ) + const starterPackName = `${displayName}'s ${starterPackString}` + + const list = await createStarterPackList({ + name: starterPackName, + profiles, + agent, + }) + + return await agent.app.bsky.graph.starterpack.create( + { + repo: agent.session!.did, + }, + { + name: starterPackName, + list: list.uri, + createdAt: new Date().toISOString(), + }, + ) + }, + onSuccess: async data => { + await whenAppViewReady(agent, data.uri, v => { + return typeof v?.data.starterPack.uri === 'string' + }) + onSuccess(data) + }, + onError: error => { + onError(error) + }, + }) +} + +function createListItem({did, listUri}: {did: string; listUri: string}) { + return { + $type: 'com.atproto.repo.applyWrites#create', + collection: 'app.bsky.graph.listitem', + value: { + $type: 'app.bsky.graph.listitem', + subject: did, + list: listUri, + createdAt: new Date().toISOString(), + }, + } +} + +async function whenAppViewReady( + agent: BskyAgent, + uri: string, + fn: (res?: AppBskyGraphGetStarterPack.Response) => boolean, +) { + await until( + 5, // 5 tries + 1e3, // 1s delay between tries + fn, + () => agent.app.bsky.graph.getStarterPack({starterPack: uri}), + ) +} diff --git a/src/lib/hooks/useBottomBarOffset.ts b/src/lib/hooks/useBottomBarOffset.ts new file mode 100644 index 0000000000..945c980620 --- /dev/null +++ b/src/lib/hooks/useBottomBarOffset.ts @@ -0,0 +1,14 @@ +import {useSafeAreaInsets} from 'react-native-safe-area-context' + +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {clamp} from 'lib/numbers' +import {isWeb} from 'platform/detection' + +export function useBottomBarOffset(modifier: number = 0) { + const {isTabletOrDesktop} = useWebMediaQueries() + const {bottom: bottomInset} = useSafeAreaInsets() + return ( + (isWeb && isTabletOrDesktop ? 0 : clamp(60 + bottomInset, 60, 75)) + + modifier + ) +} diff --git a/src/lib/hooks/useNotificationHandler.ts b/src/lib/hooks/useNotificationHandler.ts index 347062bebe..e4e7e14744 100644 --- a/src/lib/hooks/useNotificationHandler.ts +++ b/src/lib/hooks/useNotificationHandler.ts @@ -26,6 +26,7 @@ type NotificationReason = | 'reply' | 'quote' | 'chat-message' + | 'starterpack-joined' type NotificationPayload = | { @@ -142,6 +143,7 @@ export function useNotificationsHandler() { case 'mention': case 'quote': case 'reply': + case 'starterpack-joined': resetToTab('NotificationsTab') break // TODO implement these after we have an idea of how to handle each individual case diff --git a/src/lib/moderation/create-sanitized-display-name.ts b/src/lib/moderation/create-sanitized-display-name.ts new file mode 100644 index 0000000000..16135b2745 --- /dev/null +++ b/src/lib/moderation/create-sanitized-display-name.ts @@ -0,0 +1,21 @@ +import {AppBskyActorDefs} from '@atproto/api' + +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {sanitizeHandle} from 'lib/strings/handles' + +export function createSanitizedDisplayName( + profile: + | AppBskyActorDefs.ProfileViewBasic + | AppBskyActorDefs.ProfileViewDetailed, + noAt = false, +) { + if (profile.displayName != null && profile.displayName !== '') { + return sanitizeDisplayName(profile.displayName) + } else { + let sanitizedHandle = sanitizeHandle(profile.handle) + if (!noAt) { + sanitizedHandle = `@${sanitizedHandle}` + } + return sanitizedHandle + } +} diff --git a/src/lib/moderation/useReportOptions.ts b/src/lib/moderation/useReportOptions.ts index 54b727b76b..91656857e4 100644 --- a/src/lib/moderation/useReportOptions.ts +++ b/src/lib/moderation/useReportOptions.ts @@ -13,6 +13,7 @@ interface ReportOptions { account: ReportOption[] post: ReportOption[] list: ReportOption[] + starterpack: ReportOption[] feedgen: ReportOption[] other: ReportOption[] convoMessage: ReportOption[] @@ -94,6 +95,14 @@ export function useReportOptions(): ReportOptions { }, ...common, ], + starterpack: [ + { + reason: ComAtprotoModerationDefs.REASONVIOLATION, + title: _(msg`Name or Description Violates Community Standards`), + description: _(msg`Terms used violate community standards`), + }, + ...common, + ], feedgen: [ { reason: ComAtprotoModerationDefs.REASONVIOLATION, diff --git a/src/lib/routes/links.ts b/src/lib/routes/links.ts index 9dfdab909b..56b716677b 100644 --- a/src/lib/routes/links.ts +++ b/src/lib/routes/links.ts @@ -1,3 +1,5 @@ +import {AppBskyGraphDefs, AtUri} from '@atproto/api' + import {isInvalidHandle} from 'lib/strings/handles' export function makeProfileLink( @@ -35,3 +37,18 @@ export function makeSearchLink(props: {query: string; from?: 'me' | string}) { props.query + (props.from ? ` from:${props.from}` : ''), )}` } + +export function makeStarterPackLink( + starterPackOrName: + | AppBskyGraphDefs.StarterPackViewBasic + | AppBskyGraphDefs.StarterPackView + | string, + rkey?: string, +) { + if (typeof starterPackOrName === 'string') { + return `https://bsky.app/start/${starterPackOrName}/${rkey}` + } else { + const uriRkey = new AtUri(starterPackOrName.uri).rkey + return `https://bsky.app/start/${starterPackOrName.creator.handle}/${uriRkey}` + } +} diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 403c2bb675..8a173b6756 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -42,6 +42,12 @@ export type CommonNavigatorParams = { MessagesConversation: {conversation: string; embed?: string} MessagesSettings: undefined Feeds: undefined + Start: {name: string; rkey: string} + StarterPack: {name: string; rkey: string; new?: boolean} + StarterPackWizard: undefined + StarterPackEdit: { + rkey?: string + } } export type BottomTabNavigatorParams = CommonNavigatorParams & { @@ -93,6 +99,12 @@ export type AllNavigatorParams = CommonNavigatorParams & { Hashtag: {tag: string; author?: string} MessagesTab: undefined Messages: {animation?: 'push' | 'pop'} + Start: {name: string; rkey: string} + StarterPack: {name: string; rkey: string; new?: boolean} + StarterPackWizard: undefined + StarterPackEdit: { + rkey?: string + } } // NOTE diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index 2e8cedb54b..07ed8c0ca7 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -53,7 +53,14 @@ export type LogEvents = { } 'onboarding:moderation:nextPressed': {} 'onboarding:profile:nextPressed': {} - 'onboarding:finished:nextPressed': {} + 'onboarding:finished:nextPressed': { + usedStarterPack: boolean + starterPackName?: string + starterPackCreator?: string + starterPackUri?: string + profilesFollowed: number + feedsPinned: number + } 'onboarding:finished:avatarResult': { avatarResult: 'default' | 'created' | 'uploaded' } @@ -61,7 +68,12 @@ export type LogEvents = { feedUrl: string feedType: string index: number - reason: 'focus' | 'tabbar-click' | 'pager-swipe' | 'desktop-sidebar-click' + reason: + | 'focus' + | 'tabbar-click' + | 'pager-swipe' + | 'desktop-sidebar-click' + | 'starter-pack-initial-feed' } 'feed:endReached:sampled': { feedUrl: string @@ -134,6 +146,7 @@ export type LogEvents = { | 'ProfileMenu' | 'ProfileHoverCard' | 'AvatarButton' + | 'StarterPackProfilesList' } 'profile:unfollow': { logContext: @@ -146,6 +159,7 @@ export type LogEvents = { | 'ProfileHoverCard' | 'Chat' | 'AvatarButton' + | 'StarterPackProfilesList' } 'chat:create': { logContext: 'ProfileHeader' | 'NewChatDialog' | 'SendViaChatDialog' @@ -157,6 +171,23 @@ export type LogEvents = { | 'ChatsList' | 'SendViaChatDialog' } + 'starterPack:share': { + starterPack: string + shareType: 'link' | 'qrcode' + qrShareType?: 'save' | 'copy' | 'share' + } + 'starterPack:followAll': { + logContext: 'StarterPackProfilesList' | 'Onboarding' + starterPack: string + count: number + } + 'starterPack:delete': {} + 'starterPack:create': { + setName: boolean + setDescription: boolean + profilesCount: number + feedsCount: number + } 'test:all:always': {} 'test:all:sometimes': {} diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 46ef934ef6..bf2484ccb9 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -5,3 +5,4 @@ export type Gate = | 'request_notifications_permission_after_onboarding_v2' | 'show_avi_follow_button' | 'show_follow_back_label_v2' + | 'starter_packs_enabled' diff --git a/src/lib/strings/starter-pack.ts b/src/lib/strings/starter-pack.ts new file mode 100644 index 0000000000..489d0b9231 --- /dev/null +++ b/src/lib/strings/starter-pack.ts @@ -0,0 +1,101 @@ +import {AppBskyGraphDefs, AtUri} from '@atproto/api' + +export function createStarterPackLinkFromAndroidReferrer( + referrerQueryString: string, +): string | null { + try { + // The referrer string is just some URL parameters, so lets add them to a fake URL + const url = new URL('http://throwaway.com/?' + referrerQueryString) + const utmContent = url.searchParams.get('utm_content') + const utmSource = url.searchParams.get('utm_source') + + if (!utmContent) return null + if (utmSource !== 'bluesky') return null + + // This should be a string like `starterpack_haileyok.com_rkey` + const contentParts = utmContent.split('_') + + if (contentParts[0] !== 'starterpack') return null + if (contentParts.length !== 3) return null + + return `at://${contentParts[1]}/app.bsky.graph.starterpack/${contentParts[2]}` + } catch (e) { + return null + } +} + +export function parseStarterPackUri(uri?: string): { + name: string + rkey: string +} | null { + if (!uri) return null + + try { + if (uri.startsWith('at://')) { + const atUri = new AtUri(uri) + if (atUri.collection !== 'app.bsky.graph.starterpack') return null + if (atUri.rkey) { + return { + name: atUri.hostname, + rkey: atUri.rkey, + } + } + return null + } else { + const url = new URL(uri) + const parts = url.pathname.split('/') + const [_, path, name, rkey] = parts + + if (parts.length !== 4) return null + if (path !== 'starter-pack' && path !== 'start') return null + if (!name || !rkey) return null + return { + name, + rkey, + } + } + } catch (e) { + return null + } +} + +export function createStarterPackGooglePlayUri( + name: string, + rkey: string, +): string | null { + if (!name || !rkey) return null + return `https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack_${name}_${rkey}` +} + +export function httpStarterPackUriToAtUri(httpUri?: string): string | null { + if (!httpUri) return null + + const parsed = parseStarterPackUri(httpUri) + if (!parsed) return null + + if (httpUri.startsWith('at://')) return httpUri + + return `at://${parsed.name}/app.bsky.graph.starterpack/${parsed.rkey}` +} + +export function getStarterPackOgCard( + didOrStarterPack: AppBskyGraphDefs.StarterPackView | string, + rkey?: string, +) { + if (typeof didOrStarterPack === 'string') { + return `https://ogcard.cdn.bsky.app/start/${didOrStarterPack}/${rkey}` + } else { + const rkey = new AtUri(didOrStarterPack.uri).rkey + return `https://ogcard.cdn.bsky.app/start/${didOrStarterPack.creator.did}/${rkey}` + } +} + +export function createStarterPackUri({ + did, + rkey, +}: { + did: string + rkey: string +}): string | null { + return new AtUri(`at://${did}/app.bsky.graph.starterpack/${rkey}`).toString() +} diff --git a/src/routes.ts b/src/routes.ts index de711f5dc2..f241d37a07 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -41,4 +41,8 @@ export const router = new Router({ Messages: '/messages', MessagesSettings: '/messages/settings', MessagesConversation: '/messages/:conversation', + Start: '/start/:name/:rkey', + StarterPackEdit: '/starter-pack/edit/:rkey', + StarterPack: '/starter-pack/:name/:rkey', + StarterPackWizard: '/starter-pack/create', }) diff --git a/src/screens/Login/LoginForm.tsx b/src/screens/Login/LoginForm.tsx index dfa10668b6..7cfd38e34f 100644 --- a/src/screens/Login/LoginForm.tsx +++ b/src/screens/Login/LoginForm.tsx @@ -21,6 +21,7 @@ import {logger} from '#/logger' import {useSessionApi} from '#/state/session' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useRequestNotificationsPermission} from 'lib/notifications/notifications' +import {useSetHasCheckedForStarterPack} from 'state/preferences/used-starter-packs' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {FormError} from '#/components/forms/FormError' @@ -69,6 +70,7 @@ export const LoginForm = ({ const {login} = useSessionApi() const requestNotificationsPermission = useRequestNotificationsPermission() const {setShowLoggedOut} = useLoggedOutViewControls() + const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack() const onPressSelectService = React.useCallback(() => { Keyboard.dismiss() @@ -116,6 +118,7 @@ export const LoginForm = ({ 'LoginForm', ) setShowLoggedOut(false) + setHasCheckedForStarterPack(true) requestNotificationsPermission('Login') } catch (e: any) { const errMsg = e.toString() diff --git a/src/screens/Login/ScreenTransition.tsx b/src/screens/Login/ScreenTransition.tsx index ab0a223678..6fad266802 100644 --- a/src/screens/Login/ScreenTransition.tsx +++ b/src/screens/Login/ScreenTransition.tsx @@ -1,9 +1,16 @@ import React from 'react' +import {StyleProp, ViewStyle} from 'react-native' import Animated, {FadeInRight, FadeOutLeft} from 'react-native-reanimated' -export function ScreenTransition({children}: {children: React.ReactNode}) { +export function ScreenTransition({ + style, + children, +}: { + style?: StyleProp + children: React.ReactNode +}) { return ( - + {children} ) diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx index c75dd4fa74..c7a459659f 100644 --- a/src/screens/Onboarding/StepFinished.tsx +++ b/src/screens/Onboarding/StepFinished.tsx @@ -1,11 +1,18 @@ import React from 'react' import {View} from 'react-native' +import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' +import {SavedFeed} from '@atproto/api/dist/client/types/app/bsky/actor/defs' +import {TID} from '@atproto/common-web' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' import {useAnalytics} from '#/lib/analytics/analytics' -import {BSKY_APP_ACCOUNT_DID} from '#/lib/constants' +import { + BSKY_APP_ACCOUNT_DID, + DISCOVER_SAVED_FEED, + TIMELINE_SAVED_FEED, +} from '#/lib/constants' import {logEvent} from '#/lib/statsig/statsig' import {logger} from '#/logger' import {preferencesQueryKey} from '#/state/queries/preferences' @@ -14,6 +21,11 @@ import {useAgent} from '#/state/session' import {useOnboardingDispatch} from '#/state/shell' import {uploadBlob} from 'lib/api' import {useRequestNotificationsPermission} from 'lib/notifications/notifications' +import {useSetHasCheckedForStarterPack} from 'state/preferences/used-starter-packs' +import { + useActiveStarterPack, + useSetActiveStarterPack, +} from 'state/shell/starter-pack' import { DescriptionText, OnboardingControls, @@ -41,17 +53,74 @@ export function StepFinished() { const queryClient = useQueryClient() const agent = useAgent() const requestNotificationsPermission = useRequestNotificationsPermission() + const activeStarterPack = useActiveStarterPack() + const setActiveStarterPack = useSetActiveStarterPack() + const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack() const finishOnboarding = React.useCallback(async () => { setSaving(true) - const {interestsStepResults, profileStepResults} = state - const {selectedInterests} = interestsStepResults + let starterPack: AppBskyGraphDefs.StarterPackView | undefined + let listItems: AppBskyGraphDefs.ListItemView[] | undefined + + if (activeStarterPack?.uri) { + try { + const spRes = await agent.app.bsky.graph.getStarterPack({ + starterPack: activeStarterPack.uri, + }) + starterPack = spRes.data.starterPack + + if (starterPack.list) { + const listRes = await agent.app.bsky.graph.getList({ + list: starterPack.list.uri, + limit: 50, + }) + listItems = listRes.data.items + } + } catch (e) { + logger.error('Failed to fetch starter pack', {safeMessage: e}) + // don't tell the user, just get them through onboarding. + } + } + try { + const {interestsStepResults, profileStepResults} = state + const {selectedInterests} = interestsStepResults + await Promise.all([ - bulkWriteFollows(agent, [BSKY_APP_ACCOUNT_DID]), + bulkWriteFollows(agent, [ + BSKY_APP_ACCOUNT_DID, + ...(listItems?.map(i => i.subject.did) ?? []), + ]), (async () => { + // Interests need to get saved first, then we can write the feeds to prefs await agent.setInterestsPref({tags: selectedInterests}) + + // Default feeds that every user should have pinned when landing in the app + const feedsToSave: SavedFeed[] = [ + { + ...DISCOVER_SAVED_FEED, + id: TID.nextStr(), + }, + { + ...TIMELINE_SAVED_FEED, + id: TID.nextStr(), + }, + ] + + // Any starter pack feeds will be pinned _after_ the defaults + if (starterPack && starterPack.feeds?.length) { + feedsToSave.concat( + starterPack.feeds.map(f => ({ + type: 'feed', + value: f.uri, + pinned: true, + id: TID.nextStr(), + })), + ) + } + + await agent.overwriteSavedFeeds(feedsToSave) })(), (async () => { const {imageUri, imageMime} = profileStepResults @@ -63,9 +132,24 @@ export function StepFinished() { if (res.data.blob) { existing.avatar = res.data.blob } + + if (starterPack) { + existing.joinedViaStarterPack = { + uri: starterPack.uri, + cid: starterPack.cid, + } + } + + existing.displayName = '' + // HACKFIX + // creating a bunch of identical profile objects is breaking the relay + // tossing this unspecced field onto it to reduce the size of the problem + // -prf + existing.createdAt = new Date().toISOString() return existing }) } + logEvent('onboarding:finished:avatarResult', { avatarResult: profileStepResults.isCreatedAvatar ? 'created' @@ -96,19 +180,40 @@ export function StepFinished() { }) setSaving(false) + setActiveStarterPack(undefined) + setHasCheckedForStarterPack(true) dispatch({type: 'finish'}) onboardDispatch({type: 'finish'}) track('OnboardingV2:StepFinished:End') track('OnboardingV2:Complete') - logEvent('onboarding:finished:nextPressed', {}) + logEvent('onboarding:finished:nextPressed', { + usedStarterPack: Boolean(starterPack), + starterPackName: AppBskyGraphStarterpack.isRecord(starterPack?.record) + ? starterPack.record.name + : undefined, + starterPackCreator: starterPack?.creator.did, + starterPackUri: starterPack?.uri, + profilesFollowed: listItems?.length ?? 0, + feedsPinned: starterPack?.feeds?.length ?? 0, + }) + if (starterPack && listItems?.length) { + logEvent('starterPack:followAll', { + logContext: 'Onboarding', + starterPack: starterPack.uri, + count: listItems?.length, + }) + } }, [ - state, queryClient, agent, dispatch, onboardDispatch, track, + activeStarterPack, + state, requestNotificationsPermission, + setActiveStarterPack, + setHasCheckedForStarterPack, ]) React.useEffect(() => { diff --git a/src/screens/Profile/Header/DisplayName.tsx b/src/screens/Profile/Header/DisplayName.tsx index b6d88db712..c63658a44a 100644 --- a/src/screens/Profile/Header/DisplayName.tsx +++ b/src/screens/Profile/Header/DisplayName.tsx @@ -1,10 +1,10 @@ import React from 'react' import {View} from 'react-native' import {AppBskyActorDefs, ModerationDecision} from '@atproto/api' -import {sanitizeHandle} from 'lib/strings/handles' -import {sanitizeDisplayName} from 'lib/strings/display-names' -import {Shadow} from '#/state/cache/types' +import {Shadow} from '#/state/cache/types' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {sanitizeHandle} from 'lib/strings/handles' import {atoms as a, useTheme} from '#/alf' import {Text} from '#/components/Typography' diff --git a/src/screens/Signup/index.tsx b/src/screens/Signup/index.tsx index 2cc1bcab0b..3203d443cc 100644 --- a/src/screens/Signup/index.tsx +++ b/src/screens/Signup/index.tsx @@ -1,6 +1,11 @@ import React from 'react' import {View} from 'react-native' -import {LayoutAnimationConfig} from 'react-native-reanimated' +import Animated, { + FadeIn, + FadeOut, + LayoutAnimationConfig, +} from 'react-native-reanimated' +import {AppBskyGraphStarterpack} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -11,6 +16,8 @@ import {createFullHandle} from '#/lib/strings/handles' import {logger} from '#/logger' import {useServiceQuery} from '#/state/queries/service' import {useAgent} from '#/state/session' +import {useStarterPackQuery} from 'state/queries/starter-packs' +import {useActiveStarterPack} from 'state/shell/starter-pack' import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' import { initialState, @@ -26,6 +33,7 @@ import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {AppLanguageDropdown} from '#/components/AppLanguageDropdown' import {Button, ButtonText} from '#/components/Button' import {Divider} from '#/components/Divider' +import {LinearGradientBackground} from '#/components/LinearGradientBackground' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' @@ -38,6 +46,11 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { const {gtMobile} = useBreakpoints() const agent = useAgent() + const activeStarterPack = useActiveStarterPack() + const {data: starterPack} = useStarterPackQuery({ + uri: activeStarterPack?.uri, + }) + const { data: serviceInfo, isFetching, @@ -142,6 +155,31 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { description={_(msg`We're so excited to have you join us!`)} scrollable> + {state.activeStep === SignupStep.INFO && + starterPack && + AppBskyGraphStarterpack.isRecord(starterPack.record) ? ( + + + + {starterPack.record.name} + + + {starterPack.feeds?.length ? ( + + You'll follow the suggested users and feeds once you + finish creating your account! + + ) : ( + + You'll follow the suggested users once you finish creating + your account! + + )} + + + + ) : null} void +}) { + const moderationOpts = useModerationOpts() + const activeStarterPack = useActiveStarterPack() + + const {data: starterPack, isError: isErrorStarterPack} = useStarterPackQuery({ + uri: activeStarterPack?.uri, + }) + + const isValid = + starterPack && + starterPack.list && + AppBskyGraphDefs.validateStarterPackView(starterPack) && + AppBskyGraphStarterpack.validateRecord(starterPack.record) + + React.useEffect(() => { + if (isErrorStarterPack || (starterPack && !isValid)) { + setScreenState(LoggedOutScreenState.S_LoginOrCreateAccount) + } + }, [isErrorStarterPack, setScreenState, isValid, starterPack]) + + if (!starterPack || !isValid || !moderationOpts) { + return + } + + return ( + + ) +} + +function LandingScreenLoaded({ + starterPack, + setScreenState, + // TODO apply this to profile card + + moderationOpts, +}: { + starterPack: AppBskyGraphDefs.StarterPackView + setScreenState: (state: LoggedOutScreenState) => void + moderationOpts: ModerationOpts +}) { + const {record, creator, listItemsSample, feeds, joinedWeekCount} = starterPack + const {_} = useLingui() + const t = useTheme() + const activeStarterPack = useActiveStarterPack() + const setActiveStarterPack = useSetActiveStarterPack() + const {isTabletOrDesktop} = useWebMediaQueries() + const androidDialogControl = useDialogControl() + + const [appClipOverlayVisible, setAppClipOverlayVisible] = + React.useState(false) + + const listItemsCount = starterPack.list?.listItemCount ?? 0 + + const onContinue = () => { + setActiveStarterPack({ + uri: starterPack.uri, + }) + setScreenState(LoggedOutScreenState.S_CreateAccount) + } + + const onJoinPress = () => { + if (activeStarterPack?.isClip) { + setAppClipOverlayVisible(true) + postAppClipMessage({ + action: 'present', + }) + } else if (isAndroidWeb) { + androidDialogControl.open() + } else { + onContinue() + } + } + + const onJoinWithoutPress = () => { + if (activeStarterPack?.isClip) { + setAppClipOverlayVisible(true) + postAppClipMessage({ + action: 'present', + }) + } else { + setActiveStarterPack(undefined) + setScreenState(LoggedOutScreenState.S_CreateAccount) + } + } + + if (!AppBskyGraphStarterpack.isRecord(record)) { + return null + } + + return ( + + + + + + + + {record.name} + + + Starter pack by {`@${creator.handle}`} + + + + {record.description ? ( + + {record.description} + + ) : null} + + + {joinedWeekCount && joinedWeekCount >= 25 ? ( + + + + 123,659 joined this week + + + ) : null} + + + {Boolean(listItemsSample?.length) && ( + + + {listItemsCount <= 8 ? ( + You'll follow these people right away + ) : ( + + You'll follow these people and {listItemsCount - 8} others + + )} + + + {starterPack.listItemsSample?.slice(0, 8).map(item => ( + + + + ))} + + + )} + {feeds?.length ? ( + + + You'll stay updated with these feeds + + + + {feeds?.map(feed => ( + + + + ))} + + + ) : null} + + + + + + + + Download Bluesky + + + + The experience is better in the app. Download Bluesky now and we'll + pick back up where you left off. + + + + { + const rkey = new AtUri(starterPack.uri).rkey + if (!rkey) return + + const googlePlayUri = createStarterPackGooglePlayUri( + creator.handle, + rkey, + ) + if (!googlePlayUri) return + + window.location.href = googlePlayUri + }} + /> + + + + {isWeb && ( + + )} + + ) +} + +function AppClipOverlay({ + visible, + setIsVisible, +}: { + visible: boolean + setIsVisible: (visible: boolean) => void +}) { + if (!visible) return + + return ( + setIsVisible(false)}> + + {/* Webkit needs this to have a zindex of 2? */} + + + Download Bluesky to get started! + + + We'll remember the starter pack you chose and use it when you create + an account in the app. + + + + + ) +} diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx new file mode 100644 index 0000000000..46ce252364 --- /dev/null +++ b/src/screens/StarterPack/StarterPackScreen.tsx @@ -0,0 +1,627 @@ +import React from 'react' +import {View} from 'react-native' +import {Image} from 'expo-image' +import { + AppBskyGraphDefs, + AppBskyGraphGetList, + AppBskyGraphStarterpack, + AtUri, + ModerationOpts, +} from '@atproto/api' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' +import {NativeStackScreenProps} from '@react-navigation/native-stack' +import { + InfiniteData, + UseInfiniteQueryResult, + useQueryClient, +} from '@tanstack/react-query' + +import {cleanError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {useDeleteStarterPackMutation} from '#/state/queries/starter-packs' +import {HITSLOP_20} from 'lib/constants' +import {makeProfileLink, makeStarterPackLink} from 'lib/routes/links' +import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types' +import {logEvent} from 'lib/statsig/statsig' +import {getStarterPackOgCard} from 'lib/strings/starter-pack' +import {isWeb} from 'platform/detection' +import {useModerationOpts} from 'state/preferences/moderation-opts' +import {RQKEY, useListMembersQuery} from 'state/queries/list-members' +import {useResolveDidQuery} from 'state/queries/resolve-uri' +import {useShortenLink} from 'state/queries/shorten-link' +import {useStarterPackQuery} from 'state/queries/starter-packs' +import {useAgent, useSession} from 'state/session' +import * as Toast from '#/view/com/util/Toast' +import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' +import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' +import {CenteredView} from 'view/com/util/Views' +import {bulkWriteFollows} from '#/screens/Onboarding/util' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {useDialogControl} from '#/components/Dialog' +import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid' +import {Pencil_Stroke2_Corner0_Rounded as Pencil} from '#/components/icons/Pencil' +import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' +import {ListMaybePlaceholder} from '#/components/Lists' +import {Loader} from '#/components/Loader' +import * as Menu from '#/components/Menu' +import * as Prompt from '#/components/Prompt' +import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' +import {FeedsList} from '#/components/StarterPack/Main/FeedsList' +import {ProfilesList} from '#/components/StarterPack/Main/ProfilesList' +import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog' +import {ShareDialog} from '#/components/StarterPack/ShareDialog' +import {Text} from '#/components/Typography' + +type StarterPackScreeProps = NativeStackScreenProps< + CommonNavigatorParams, + 'StarterPack' +> + +export function StarterPackScreen({route}: StarterPackScreeProps) { + const {_} = useLingui() + const {currentAccount} = useSession() + + const {name, rkey} = route.params + const moderationOpts = useModerationOpts() + const { + data: did, + isLoading: isLoadingDid, + isError: isErrorDid, + } = useResolveDidQuery(name) + const { + data: starterPack, + isLoading: isLoadingStarterPack, + isError: isErrorStarterPack, + } = useStarterPackQuery({did, rkey}) + const listMembersQuery = useListMembersQuery(starterPack?.list?.uri, 50) + + const isValid = + starterPack && + (starterPack.list || starterPack?.creator?.did === currentAccount?.did) && + AppBskyGraphDefs.validateStarterPackView(starterPack) && + AppBskyGraphStarterpack.validateRecord(starterPack.record) + + if (!did || !starterPack || !isValid || !moderationOpts) { + return ( + + ) + } + + if (!starterPack.list && starterPack.creator.did === currentAccount?.did) { + return + } + + return ( + + ) +} + +function StarterPackScreenInner({ + starterPack, + routeParams, + listMembersQuery, + moderationOpts, +}: { + starterPack: AppBskyGraphDefs.StarterPackView + routeParams: StarterPackScreeProps['route']['params'] + listMembersQuery: UseInfiniteQueryResult< + InfiniteData + > + moderationOpts: ModerationOpts +}) { + const tabs = [ + ...(starterPack.list ? ['People'] : []), + ...(starterPack.feeds?.length ? ['Feeds'] : []), + ] + + const qrCodeDialogControl = useDialogControl() + const shareDialogControl = useDialogControl() + + const shortenLink = useShortenLink() + const [link, setLink] = React.useState() + const [imageLoaded, setImageLoaded] = React.useState(false) + + const onOpenShareDialog = React.useCallback(() => { + const rkey = new AtUri(starterPack.uri).rkey + shortenLink(makeStarterPackLink(starterPack.creator.did, rkey)).then( + res => { + setLink(res.url) + }, + ) + Image.prefetch(getStarterPackOgCard(starterPack)) + .then(() => { + setImageLoaded(true) + }) + .catch(() => { + setImageLoaded(true) + }) + shareDialogControl.open() + }, [shareDialogControl, shortenLink, starterPack]) + + React.useEffect(() => { + if (routeParams.new) { + onOpenShareDialog() + } + }, [onOpenShareDialog, routeParams.new, shareDialogControl]) + + return ( + + + ( +
+ )}> + {starterPack.list != null + ? ({headerHeight, scrollElRef}) => ( + + ) + : null} + {starterPack.feeds != null + ? ({headerHeight, scrollElRef}) => ( + + ) + : null} + + + + + + + ) +} + +function Header({ + starterPack, + routeParams, + onOpenShareDialog, +}: { + starterPack: AppBskyGraphDefs.StarterPackView + routeParams: StarterPackScreeProps['route']['params'] + onOpenShareDialog: () => void +}) { + const {_} = useLingui() + const t = useTheme() + const {currentAccount} = useSession() + const agent = useAgent() + const queryClient = useQueryClient() + + const [isProcessing, setIsProcessing] = React.useState(false) + + const {record, creator} = starterPack + const isOwn = creator?.did === currentAccount?.did + const joinedAllTimeCount = starterPack.joinedAllTimeCount ?? 0 + + const onFollowAll = async () => { + if (!starterPack.list) return + + setIsProcessing(true) + + try { + const list = await agent.app.bsky.graph.getList({ + list: starterPack.list.uri, + }) + const dids = list.data.items + .filter(li => !li.subject.viewer?.following) + .map(li => li.subject.did) + + await bulkWriteFollows(agent, dids) + + await queryClient.refetchQueries({ + queryKey: RQKEY(starterPack.list.uri), + }) + + logEvent('starterPack:followAll', { + logContext: 'StarterPackProfilesList', + starterPack: starterPack.uri, + count: dids.length, + }) + Toast.show(_(msg`All accounts have been followed!`)) + } catch (e) { + Toast.show(_(msg`An error occurred while trying to follow all`)) + } finally { + setIsProcessing(false) + } + } + + if (!AppBskyGraphStarterpack.isRecord(record)) { + return null + } + + return ( + <> + + + {isOwn ? ( + + ) : ( + + )} + + + + {record.description || joinedAllTimeCount >= 25 ? ( + + {record.description ? ( + + {record.description} + + ) : null} + {joinedAllTimeCount >= 25 ? ( + + + + + {starterPack.joinedAllTimeCount || 0} people have used this + starter pack! + + + + ) : null} + + ) : null} + + ) +} + +function OverflowMenu({ + starterPack, + routeParams, + onOpenShareDialog, +}: { + starterPack: AppBskyGraphDefs.StarterPackView + routeParams: StarterPackScreeProps['route']['params'] + onOpenShareDialog: () => void +}) { + const t = useTheme() + const {_} = useLingui() + const {gtMobile} = useBreakpoints() + const {currentAccount} = useSession() + const reportDialogControl = useReportDialogControl() + const deleteDialogControl = useDialogControl() + const navigation = useNavigation() + + const { + mutate: deleteStarterPack, + isPending: isDeletePending, + error: deleteError, + } = useDeleteStarterPackMutation({ + onSuccess: () => { + logEvent('starterPack:delete', {}) + deleteDialogControl.close(() => { + if (navigation.canGoBack()) { + navigation.popToTop() + } else { + navigation.navigate('Home') + } + }) + }, + onError: e => { + logger.error('Failed to delete starter pack', {safeMessage: e}) + }, + }) + + const isOwn = starterPack.creator.did === currentAccount?.did + + const onDeleteStarterPack = async () => { + if (!starterPack.list) { + logger.error(`Unable to delete starterpack because list is missing`) + return + } + + deleteStarterPack({ + rkey: routeParams.rkey, + listUri: starterPack.list.uri, + }) + logEvent('starterPack:delete', {}) + } + + return ( + <> + + + {({props}) => ( + + )} + + + {isOwn ? ( + <> + { + navigation.navigate('StarterPackEdit', { + rkey: routeParams.rkey, + }) + }}> + + Edit + + + + { + deleteDialogControl.open() + }}> + + Delete + + + + + ) : ( + <> + + + + Share link + + + + + + + + Report starter pack + + + + + )} + + + + {starterPack.list && ( + + )} + + + + Delete starter pack? + + + Are you sure you want delete this starter pack? + + {deleteError && ( + + + + Unable to delete + + {cleanError(deleteError)} + + + + )} + + + + + + + ) +} + +function InvalidStarterPack({rkey}: {rkey: string}) { + const {_} = useLingui() + const t = useTheme() + const navigation = useNavigation() + const {gtMobile} = useBreakpoints() + const [isProcessing, setIsProcessing] = React.useState(false) + + const goBack = () => { + if (navigation.canGoBack()) { + navigation.goBack() + } else { + navigation.replace('Home') + } + } + + const {mutate: deleteStarterPack} = useDeleteStarterPackMutation({ + onSuccess: () => { + setIsProcessing(false) + goBack() + }, + onError: e => { + setIsProcessing(false) + logger.error('Failed to delete invalid starter pack', {safeMessage: e}) + Toast.show(_(msg`Failed to delete starter pack`)) + }, + }) + + return ( + + + + Starter pack is invalid + + + + The starter pack that you are trying to view is invalid. You may + delete this starter pack instead. + + + + + + + + + ) +} diff --git a/src/screens/StarterPack/Wizard/State.tsx b/src/screens/StarterPack/Wizard/State.tsx new file mode 100644 index 0000000000..ea9bbf9d33 --- /dev/null +++ b/src/screens/StarterPack/Wizard/State.tsx @@ -0,0 +1,163 @@ +import React from 'react' +import { + AppBskyActorDefs, + AppBskyGraphDefs, + AppBskyGraphStarterpack, +} from '@atproto/api' +import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' +import {msg} from '@lingui/macro' + +import {useSession} from 'state/session' +import * as Toast from '#/view/com/util/Toast' + +const steps = ['Details', 'Profiles', 'Feeds'] as const +type Step = (typeof steps)[number] + +type Action = + | {type: 'Next'} + | {type: 'Back'} + | {type: 'SetCanNext'; canNext: boolean} + | {type: 'SetName'; name: string} + | {type: 'SetDescription'; description: string} + | {type: 'AddProfile'; profile: AppBskyActorDefs.ProfileViewBasic} + | {type: 'RemoveProfile'; profileDid: string} + | {type: 'AddFeed'; feed: GeneratorView} + | {type: 'RemoveFeed'; feedUri: string} + | {type: 'SetProcessing'; processing: boolean} + | {type: 'SetError'; error: string} + +interface State { + canNext: boolean + currentStep: Step + name?: string + description?: string + profiles: AppBskyActorDefs.ProfileViewBasic[] + feeds: GeneratorView[] + processing: boolean + error?: string + transitionDirection: 'Backward' | 'Forward' +} + +type TStateContext = [State, (action: Action) => void] + +const StateContext = React.createContext([ + {} as State, + (_: Action) => {}, +]) +export const useWizardState = () => React.useContext(StateContext) + +function reducer(state: State, action: Action): State { + let updatedState = state + + // -- Navigation + const currentIndex = steps.indexOf(state.currentStep) + if (action.type === 'Next' && state.currentStep !== 'Feeds') { + updatedState = { + ...state, + currentStep: steps[currentIndex + 1], + transitionDirection: 'Forward', + } + } else if (action.type === 'Back' && state.currentStep !== 'Details') { + updatedState = { + ...state, + currentStep: steps[currentIndex - 1], + transitionDirection: 'Backward', + } + } + + switch (action.type) { + case 'SetName': + updatedState = {...state, name: action.name.slice(0, 50)} + break + case 'SetDescription': + updatedState = {...state, description: action.description} + break + case 'AddProfile': + if (state.profiles.length >= 51) { + Toast.show(msg`You may only add up to 50 profiles`.message ?? '') + } else { + updatedState = {...state, profiles: [...state.profiles, action.profile]} + } + break + case 'RemoveProfile': + updatedState = { + ...state, + profiles: state.profiles.filter( + profile => profile.did !== action.profileDid, + ), + } + break + case 'AddFeed': + if (state.feeds.length >= 50) { + Toast.show(msg`You may only add up to 50 feeds`.message ?? '') + } else { + updatedState = {...state, feeds: [...state.feeds, action.feed]} + } + break + case 'RemoveFeed': + updatedState = { + ...state, + feeds: state.feeds.filter(f => f.uri !== action.feedUri), + } + break + case 'SetProcessing': + updatedState = {...state, processing: action.processing} + break + } + + return updatedState +} + +// TODO supply the initial state to this component +export function Provider({ + starterPack, + listItems, + children, +}: { + starterPack?: AppBskyGraphDefs.StarterPackView + listItems?: AppBskyGraphDefs.ListItemView[] + children: React.ReactNode +}) { + const {currentAccount} = useSession() + + const createInitialState = (): State => { + if (starterPack && AppBskyGraphStarterpack.isRecord(starterPack.record)) { + return { + canNext: true, + currentStep: 'Details', + name: starterPack.record.name, + description: starterPack.record.description, + profiles: + listItems + ?.map(i => i.subject) + .filter(p => p.did !== currentAccount?.did) ?? [], + feeds: starterPack.feeds ?? [], + processing: false, + transitionDirection: 'Forward', + } + } + + return { + canNext: true, + currentStep: 'Details', + profiles: [], + feeds: [], + processing: false, + transitionDirection: 'Forward', + } + } + + const [state, dispatch] = React.useReducer(reducer, null, createInitialState) + + return ( + + {children} + + ) +} + +export { + type Action as WizardAction, + type State as WizardState, + type Step as WizardStep, +} diff --git a/src/screens/StarterPack/Wizard/StepDetails.tsx b/src/screens/StarterPack/Wizard/StepDetails.tsx new file mode 100644 index 0000000000..24c992c60b --- /dev/null +++ b/src/screens/StarterPack/Wizard/StepDetails.tsx @@ -0,0 +1,84 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useProfileQuery} from 'state/queries/profile' +import {useSession} from 'state/session' +import {useWizardState} from '#/screens/StarterPack/Wizard/State' +import {atoms as a, useTheme} from '#/alf' +import * as TextField from '#/components/forms/TextField' +import {StarterPack} from '#/components/icons/StarterPack' +import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' +import {Text} from '#/components/Typography' + +export function StepDetails() { + const {_} = useLingui() + const t = useTheme() + const [state, dispatch] = useWizardState() + + const {currentAccount} = useSession() + const {data: currentProfile} = useProfileQuery({ + did: currentAccount?.did, + staleTime: 300, + }) + + return ( + + + + + + Invites, but personal + + + + Invite your friends to follow your favorite feeds and people + + + + + + What do you want to call your starter pack? + + + dispatch({type: 'SetName', name: text})} + /> + + + {state.name?.length ?? 0}/50 + + + + + + + Tell us a little more + + + + dispatch({type: 'SetDescription', description: text}) + } + multiline + style={{minHeight: 150}} + /> + + + + + ) +} diff --git a/src/screens/StarterPack/Wizard/StepFeeds.tsx b/src/screens/StarterPack/Wizard/StepFeeds.tsx new file mode 100644 index 0000000000..6752a95db3 --- /dev/null +++ b/src/screens/StarterPack/Wizard/StepFeeds.tsx @@ -0,0 +1,113 @@ +import React, {useState} from 'react' +import {ListRenderItemInfo, View} from 'react-native' +import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' +import {AppBskyFeedDefs, ModerationOpts} from '@atproto/api' +import {Trans} from '@lingui/macro' + +import {useA11y} from '#/state/a11y' +import {DISCOVER_FEED_URI} from 'lib/constants' +import { + useGetPopularFeedsQuery, + useSavedFeeds, + useSearchPopularFeedsQuery, +} from 'state/queries/feed' +import {SearchInput} from 'view/com/util/forms/SearchInput' +import {List} from 'view/com/util/List' +import {useWizardState} from '#/screens/StarterPack/Wizard/State' +import {atoms as a, useTheme} from '#/alf' +import {useThrottledValue} from '#/components/hooks/useThrottledValue' +import {Loader} from '#/components/Loader' +import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' +import {WizardFeedCard} from '#/components/StarterPack/Wizard/WizardListCard' +import {Text} from '#/components/Typography' + +function keyExtractor(item: AppBskyFeedDefs.GeneratorView) { + return item.uri +} + +export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) { + const t = useTheme() + const [state, dispatch] = useWizardState() + const [query, setQuery] = useState('') + const throttledQuery = useThrottledValue(query, 500) + const {screenReaderEnabled} = useA11y() + + const {data: savedFeedsAndLists} = useSavedFeeds() + const savedFeeds = savedFeedsAndLists?.feeds + .filter(f => f.type === 'feed' && f.view.uri !== DISCOVER_FEED_URI) + .map(f => f.view) as AppBskyFeedDefs.GeneratorView[] + + const {data: popularFeedsPages, fetchNextPage} = useGetPopularFeedsQuery({ + limit: 30, + }) + const popularFeeds = + popularFeedsPages?.pages + .flatMap(page => page.feeds) + .filter(f => !savedFeeds?.some(sf => sf?.uri === f.uri)) ?? [] + + const suggestedFeeds = savedFeeds?.concat(popularFeeds) + + const {data: searchedFeeds, isLoading: isLoadingSearch} = + useSearchPopularFeedsQuery({q: throttledQuery}) + + const renderItem = ({ + item, + }: ListRenderItemInfo) => { + return ( + + ) + } + + return ( + + + + setQuery(t)} + onPressCancelSearch={() => setQuery('')} + onSubmitQuery={() => {}} + /> + + + fetchNextPage() : undefined + } + onEndReachedThreshold={2} + renderScrollComponent={props => } + keyboardShouldPersistTaps="handled" + containWeb={true} + sideBorders={false} + style={{flex: 1}} + ListEmptyComponent={ + + {isLoadingSearch ? ( + + ) : ( + + No feeds found. Try searching for something else. + + )} + + } + /> + + ) +} diff --git a/src/screens/StarterPack/Wizard/StepFinished.tsx b/src/screens/StarterPack/Wizard/StepFinished.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/screens/StarterPack/Wizard/StepProfiles.tsx b/src/screens/StarterPack/Wizard/StepProfiles.tsx new file mode 100644 index 0000000000..8fe7f52fed --- /dev/null +++ b/src/screens/StarterPack/Wizard/StepProfiles.tsx @@ -0,0 +1,101 @@ +import React, {useState} from 'react' +import {ListRenderItemInfo, View} from 'react-native' +import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' +import {AppBskyActorDefs, ModerationOpts} from '@atproto/api' +import {Trans} from '@lingui/macro' + +import {useA11y} from '#/state/a11y' +import {isNative} from 'platform/detection' +import {useActorAutocompleteQuery} from 'state/queries/actor-autocomplete' +import {useActorSearchPaginated} from 'state/queries/actor-search' +import {SearchInput} from 'view/com/util/forms/SearchInput' +import {List} from 'view/com/util/List' +import {useWizardState} from '#/screens/StarterPack/Wizard/State' +import {atoms as a, useTheme} from '#/alf' +import {Loader} from '#/components/Loader' +import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' +import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard' +import {Text} from '#/components/Typography' + +function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) { + return item?.did ?? '' +} + +export function StepProfiles({ + moderationOpts, +}: { + moderationOpts: ModerationOpts +}) { + const t = useTheme() + const [state, dispatch] = useWizardState() + const [query, setQuery] = useState('') + const {screenReaderEnabled} = useA11y() + + const {data: topPages, fetchNextPage} = useActorSearchPaginated({ + query: encodeURIComponent('*'), + }) + const topFollowers = topPages?.pages.flatMap(p => p.actors) + + const {data: results, isLoading: isLoadingResults} = + useActorAutocompleteQuery(query, true, 12) + + const renderItem = ({ + item, + }: ListRenderItemInfo) => { + return ( + + ) + } + + return ( + + + + setQuery('')} + onSubmitQuery={() => {}} + /> + + + } + keyboardShouldPersistTaps="handled" + containWeb={true} + sideBorders={false} + style={[a.flex_1]} + onEndReached={ + !query && !screenReaderEnabled ? () => fetchNextPage() : undefined + } + onEndReachedThreshold={isNative ? 2 : 0.25} + ListEmptyComponent={ + + {isLoadingResults ? ( + + ) : ( + + Nobody was found. Try searching for someone else. + + )} + + } + /> + + ) +} diff --git a/src/screens/StarterPack/Wizard/index.tsx b/src/screens/StarterPack/Wizard/index.tsx new file mode 100644 index 0000000000..76691dc985 --- /dev/null +++ b/src/screens/StarterPack/Wizard/index.tsx @@ -0,0 +1,575 @@ +import React from 'react' +import {Keyboard, TouchableOpacity, View} from 'react-native' +import { + KeyboardAwareScrollView, + useKeyboardController, +} from 'react-native-keyboard-controller' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {Image} from 'expo-image' +import { + AppBskyActorDefs, + AppBskyGraphDefs, + AtUri, + ModerationOpts, +} from '@atproto/api' +import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Plural, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useFocusEffect, useNavigation} from '@react-navigation/native' +import {NativeStackScreenProps} from '@react-navigation/native-stack' + +import {logger} from '#/logger' +import {HITSLOP_10} from 'lib/constants' +import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types' +import {logEvent} from 'lib/statsig/statsig' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {sanitizeHandle} from 'lib/strings/handles' +import {enforceLen} from 'lib/strings/helpers' +import { + getStarterPackOgCard, + parseStarterPackUri, +} from 'lib/strings/starter-pack' +import {isAndroid, isNative, isWeb} from 'platform/detection' +import {useModerationOpts} from 'state/preferences/moderation-opts' +import {useListMembersQuery} from 'state/queries/list-members' +import {useProfileQuery} from 'state/queries/profile' +import { + useCreateStarterPackMutation, + useEditStarterPackMutation, + useStarterPackQuery, +} from 'state/queries/starter-packs' +import {useSession} from 'state/session' +import {useSetMinimalShellMode} from 'state/shell' +import * as Toast from '#/view/com/util/Toast' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {CenteredView} from 'view/com/util/Views' +import {useWizardState, WizardStep} from '#/screens/StarterPack/Wizard/State' +import {StepDetails} from '#/screens/StarterPack/Wizard/StepDetails' +import {StepFeeds} from '#/screens/StarterPack/Wizard/StepFeeds' +import {StepProfiles} from '#/screens/StarterPack/Wizard/StepProfiles' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import {useDialogControl} from '#/components/Dialog' +import {ListMaybePlaceholder} from '#/components/Lists' +import {Loader} from '#/components/Loader' +import {WizardEditListDialog} from '#/components/StarterPack/Wizard/WizardEditListDialog' +import {Text} from '#/components/Typography' +import {Provider} from './State' + +export function Wizard({ + route, +}: NativeStackScreenProps< + CommonNavigatorParams, + 'StarterPackEdit' | 'StarterPackWizard' +>) { + const {rkey} = route.params ?? {} + const {currentAccount} = useSession() + const moderationOpts = useModerationOpts() + + const {_} = useLingui() + + const { + data: starterPack, + isLoading: isLoadingStarterPack, + isError: isErrorStarterPack, + } = useStarterPackQuery({did: currentAccount!.did, rkey}) + const listUri = starterPack?.list?.uri + + const { + data: profilesData, + isLoading: isLoadingProfiles, + isError: isErrorProfiles, + } = useListMembersQuery(listUri, 50) + const listItems = profilesData?.pages.flatMap(p => p.items) + + const { + data: profile, + isLoading: isLoadingProfile, + isError: isErrorProfile, + } = useProfileQuery({did: currentAccount?.did}) + + const isEdit = Boolean(rkey) + const isReady = + (!isEdit || (isEdit && starterPack && listItems)) && + profile && + moderationOpts + + if (!isReady) { + return ( + + ) + } else if (isEdit && starterPack?.creator.did !== currentAccount?.did) { + return ( + + ) + } + + return ( + + + + ) +} + +function WizardInner({ + currentStarterPack, + currentListItems, + profile, + moderationOpts, +}: { + currentStarterPack?: AppBskyGraphDefs.StarterPackView + currentListItems?: AppBskyGraphDefs.ListItemView[] + profile: AppBskyActorDefs.ProfileViewBasic + moderationOpts: ModerationOpts +}) { + const navigation = useNavigation() + const {_} = useLingui() + const t = useTheme() + const setMinimalShellMode = useSetMinimalShellMode() + const {setEnabled} = useKeyboardController() + const [state, dispatch] = useWizardState() + const {currentAccount} = useSession() + const {data: currentProfile} = useProfileQuery({ + did: currentAccount?.did, + staleTime: 0, + }) + const parsed = parseStarterPackUri(currentStarterPack?.uri) + + React.useEffect(() => { + navigation.setOptions({ + gestureEnabled: false, + }) + }, [navigation]) + + useFocusEffect( + React.useCallback(() => { + setEnabled(true) + setMinimalShellMode(true) + + return () => { + setMinimalShellMode(false) + setEnabled(false) + } + }, [setMinimalShellMode, setEnabled]), + ) + + const getDefaultName = () => { + let displayName + if ( + currentProfile?.displayName != null && + currentProfile?.displayName !== '' + ) { + displayName = sanitizeDisplayName(currentProfile.displayName) + } else { + displayName = sanitizeHandle(currentProfile!.handle) + } + return _(msg`${displayName}'s Starter Pack`).slice(0, 50) + } + + const wizardUiStrings: Record< + WizardStep, + {header: string; nextBtn: string; subtitle?: string} + > = { + Details: { + header: _(msg`Starter Pack`), + nextBtn: _(msg`Next`), + }, + Profiles: { + header: _(msg`People`), + nextBtn: _(msg`Next`), + subtitle: _( + msg`Add people to your starter pack that you think others will enjoy following`, + ), + }, + Feeds: { + header: _(msg`Feeds`), + nextBtn: state.feeds.length === 0 ? _(msg`Skip`) : _(msg`Finish`), + subtitle: _(msg`Some subtitle`), + }, + } + const currUiStrings = wizardUiStrings[state.currentStep] + + const onSuccessCreate = (data: {uri: string; cid: string}) => { + const rkey = new AtUri(data.uri).rkey + logEvent('starterPack:create', { + setName: state.name != null, + setDescription: state.description != null, + profilesCount: state.profiles.length, + feedsCount: state.feeds.length, + }) + Image.prefetch([getStarterPackOgCard(currentProfile!.did, rkey)]) + dispatch({type: 'SetProcessing', processing: false}) + navigation.replace('StarterPack', { + name: currentAccount!.handle, + rkey, + new: true, + }) + } + + const onSuccessEdit = () => { + if (navigation.canGoBack()) { + navigation.goBack() + } else { + navigation.replace('StarterPack', { + name: currentAccount!.handle, + rkey: parsed!.rkey, + }) + } + } + + const {mutate: createStarterPack} = useCreateStarterPackMutation({ + onSuccess: onSuccessCreate, + onError: e => { + logger.error('Failed to create starter pack', {safeMessage: e}) + dispatch({type: 'SetProcessing', processing: false}) + Toast.show(_(msg`Failed to create starter pack`)) + }, + }) + const {mutate: editStarterPack} = useEditStarterPackMutation({ + onSuccess: onSuccessEdit, + onError: e => { + logger.error('Failed to edit starter pack', {safeMessage: e}) + dispatch({type: 'SetProcessing', processing: false}) + Toast.show(_(msg`Failed to create starter pack`)) + }, + }) + + const submit = async () => { + dispatch({type: 'SetProcessing', processing: true}) + if (currentStarterPack && currentListItems) { + editStarterPack({ + name: state.name ?? getDefaultName(), + description: state.description, + descriptionFacets: [], + profiles: state.profiles, + feeds: state.feeds, + currentStarterPack: currentStarterPack, + currentListItems: currentListItems, + }) + } else { + createStarterPack({ + name: state.name ?? getDefaultName(), + description: state.description, + descriptionFacets: [], + profiles: state.profiles, + feeds: state.feeds, + }) + } + } + + const onNext = () => { + if (state.currentStep === 'Feeds') { + submit() + return + } + + const keyboardVisible = Keyboard.isVisible() + Keyboard.dismiss() + setTimeout( + () => { + dispatch({type: 'Next'}) + }, + keyboardVisible ? 16 : 0, + ) + } + + return ( + + + + { + if (state.currentStep === 'Details') { + navigation.pop() + } else { + dispatch({type: 'Back'}) + } + }}> + + + + + {currUiStrings.header} + + + + + + {state.currentStep === 'Details' ? ( + + ) : state.currentStep === 'Profiles' ? ( + + ) : state.currentStep === 'Feeds' ? ( + + ) : null} + + + {state.currentStep !== 'Details' && ( +