diff options
243 files changed, 16775 insertions, 10246 deletions
diff --git a/.github/workflows/build-and-push-bskyweb-aws.yaml b/.github/workflows/build-and-push-bskyweb-aws.yaml index fef24f952..3f6070579 100644 --- a/.github/workflows/build-and-push-bskyweb-aws.yaml +++ b/.github/workflows/build-and-push-bskyweb-aws.yaml @@ -3,8 +3,8 @@ on: push: branches: - main - - traffic-reduction - - respect-optout-for-embeds + - 3p-moderators + env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }} diff --git a/.github/workflows/bundle-deploy-eas-update.yml b/.github/workflows/bundle-deploy-eas-update.yml new file mode 100644 index 000000000..72a38eaa6 --- /dev/null +++ b/.github/workflows/bundle-deploy-eas-update.yml @@ -0,0 +1,55 @@ +--- +name: Bundle and Deploy EAS Update + +on: + workflow_dispatch: + inputs: + runtimeVersion: + type: string + description: Runtime version (in x.x.x format) that this update is for + required: true + +jobs: + bundleDeploy: + name: Bundle and Deploy EAS Update + runs-on: ubuntu-latest + steps: + - name: 🧠Validate version + run: | + [[ "${{ github.event.inputs.runtimeVersion }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "Version is valid" || exit 1 + + - name: â¬‡ï¸ Checkout + uses: actions/checkout@v4 + + - name: 🔧 Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: yarn + + - name: âš™ï¸ Install Dependencies + run: yarn install + + - name: 🪛 Install jq + uses: dcarbone/install-jq-action@v2 + + - name: â›ï¸ Setup Expo + run: yarn global add eas-cli-local-build-plugin + + - name: 🔤 Compile Translations + run: yarn intl:build + + - name: âœï¸ Write environment variables + run: | + export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}' + echo "${{ secrets.ENV_TOKEN }}" > .env + echo "$json" > google-services.json + + - name: ðŸ—ï¸ Create Bundle + run: yarn export + + - name: 📦 Package Bundle and 🚀 Deploy + run: yarn make-deploy-bundle + env: + DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }} + RUNTIME_VERSION: ${{ github.event.inputs.runtimeVersion }} diff --git a/.github/workflows/pull-request-commit.yml b/.github/workflows/pull-request-commit.yml index 72381a32d..85ebae4db 100644 --- a/.github/workflows/pull-request-commit.yml +++ b/.github/workflows/pull-request-commit.yml @@ -114,7 +114,7 @@ jobs: id: old_comment with: issue-number: ${{ github.event.pull_request.number }} - comment-author: 'expo-bot' + comment-author: 'github-actions[bot]' body-includes: <!-- pr-labeler comment --> - name: 💬 Add comment with fingerprint @@ -182,4 +182,4 @@ jobs: comment_id: '${{ steps.old_comment.outputs.comment-id }}', owner: context.repo.owner, repo: context.repo.repo, - }); \ No newline at end of file + }); diff --git a/__tests__/lib/strings/url-helpers.test.ts b/__tests__/lib/strings/url-helpers.test.ts index 6ac31aeb6..fb4b8f755 100644 --- a/__tests__/lib/strings/url-helpers.test.ts +++ b/__tests__/lib/strings/url-helpers.test.ts @@ -4,6 +4,7 @@ import { linkRequiresWarning, isPossiblyAUrl, splitApexDomain, + isTrustedUrl, } from '../../../src/lib/strings/url-helpers' describe('linkRequiresWarning', () => { @@ -74,6 +75,10 @@ describe('linkRequiresWarning', () => { // bad uri inputs, default to true ['', '', true], ['example.com', 'example.com', true], + ['/profile', 'Username', false], + ['#', 'Show More', false], + ['https://docs.bsky.app', 'https://docs.bsky.app', false], + ['https://bsky.app/compose/intent?text=test', 'Compose a post', false], ] it.each(cases)( @@ -139,3 +144,36 @@ describe('splitApexDomain', () => { }, ) }) + +describe('isTrustedUrl', () => { + const cases = [ + ['#', true], + ['#profile', true], + ['/', true], + ['/profile', true], + ['/profile/', true], + ['/profile/bob.test', true], + ['https://bsky.app', true], + ['https://bsky.app/', true], + ['https://bsky.app/profile/bob.test', true], + ['https://www.bsky.app', true], + ['https://www.bsky.app/', true], + ['https://docs.bsky.app', true], + ['https://bsky.social', true], + ['https://bsky.social/blog', true], + ['https://blueskyweb.xyz', true], + ['https://blueskyweb.zendesk.com', true], + ['http://bsky.app', true], + ['http://bsky.social', true], + ['http://blueskyweb.xyz', true], + ['http://blueskyweb.zendesk.com', true], + ['https://google.com', false], + ['https://docs.google.com', false], + ['https://google.com/#', false], + ] + + it.each(cases)('given input uri %p, returns %p', (str, expected) => { + const output = isTrustedUrl(str) + expect(output).toEqual(expected) + }) +}) diff --git a/assets/icons/arrowTriangleBottom_stroke2_corner1_rounded.svg b/assets/icons/arrowTriangleBottom_stroke2_corner1_rounded.svg new file mode 100644 index 000000000..f40546f7c --- /dev/null +++ b/assets/icons/arrowTriangleBottom_stroke2_corner1_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M4.213 6.886c-.673-1.35.334-2.889 1.806-2.889H17.98c1.472 0 2.479 1.539 1.806 2.89l-5.982 11.997c-.74 1.484-2.87 1.484-3.61 0L4.213 6.886Z"/></svg> \ No newline at end of file diff --git a/assets/icons/bars3_stroke2_corner0_rounded.svg b/assets/icons/bars3_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..cbcb531a6 --- /dev/null +++ b/assets/icons/bars3_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3 5a1 1 0 0 0 0 2h18a1 1 0 1 0 0-2H3Zm-1 7a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm0 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg> diff --git a/assets/icons/chevronBottom_stroke2_corner0_rounded.svg b/assets/icons/chevronBottom_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..705c1c513 --- /dev/null +++ b/assets/icons/chevronBottom_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3.293 8.293a1 1 0 0 1 1.414 0L12 15.586l7.293-7.293a1 1 0 1 1 1.414 1.414l-8 8a1 1 0 0 1-1.414 0l-8-8a1 1 0 0 1 0-1.414Z" clip-rule="evenodd"/></svg> \ No newline at end of file diff --git a/assets/icons/chevronTop_stroke2_corner0_rounded.svg b/assets/icons/chevronTop_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..da94ba911 --- /dev/null +++ b/assets/icons/chevronTop_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 6a1 1 0 0 1 .707.293l8 8a1 1 0 0 1-1.414 1.414L12 8.414l-7.293 7.293a1 1 0 0 1-1.414-1.414l8-8A1 1 0 0 1 12 6Z" clip-rule="evenodd"/></svg> \ No newline at end of file diff --git a/assets/icons/circleBanSign_stroke2_corner0_rounded.svg b/assets/icons/circleBanSign_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..73251477f --- /dev/null +++ b/assets/icons/circleBanSign_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 4a8 8 0 0 0-6.32 12.906L16.906 5.68A7.962 7.962 0 0 0 12 4Zm6.32 3.094L7.094 18.32A8 8 0 0 0 18.32 7.094ZM2 12C2 6.477 6.477 2 12 2a9.972 9.972 0 0 1 7.071 2.929A9.972 9.972 0 0 1 22 12c0 5.523-4.477 10-10 10a9.972 9.972 0 0 1-7.071-2.929A9.972 9.972 0 0 1 2 12Z" clip-rule="evenodd"/></svg> diff --git a/assets/icons/group3_stroke2_corner0_rounded.svg b/assets/icons/group3_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..2a8f43a8a --- /dev/null +++ b/assets/icons/group3_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M8 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM4 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm13-1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm-3.5 1.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Zm5.826 7.376c-.919-.779-2.052-1.03-3.1-.787a1 1 0 0 1-.451-1.949c1.671-.386 3.45.028 4.844 1.211 1.397 1.185 2.348 3.084 2.524 5.579a1 1 0 0 1-.997 1.07H18a1 1 0 1 1 0-2h3.007c-.29-1.47-.935-2.49-1.681-3.124ZM3.126 19h9.747c-.61-3.495-2.867-5-4.873-5-2.006 0-4.263 1.505-4.873 5ZM8 12c3.47 0 6.64 2.857 6.998 7.93A1 1 0 0 1 14 21H2a1 1 0 0 1-.998-1.07C1.36 14.857 4.53 12 8 12Z" clip-rule="evenodd"/></svg> diff --git a/assets/icons/heart2_filled_stroke2_corner0_rounded.svg b/assets/icons/heart2_filled_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..1dfefb4c9 --- /dev/null +++ b/assets/icons/heart2_filled_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M12.489 21.372c8.528-4.78 10.626-10.47 9.022-14.47-.779-1.941-2.414-3.333-4.342-3.763-1.697-.378-3.552.003-5.169 1.287-1.617-1.284-3.472-1.665-5.17-1.287-1.927.43-3.562 1.822-4.34 3.764-1.605 4 .493 9.69 9.021 14.47a1 1 0 0 0 .978 0Z"/></svg> diff --git a/assets/icons/heart2_stroke2_corner0_rounded.svg b/assets/icons/heart2_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..5b3da8e00 --- /dev/null +++ b/assets/icons/heart2_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M16.734 5.091c-1.238-.276-2.708.047-4.022 1.38a1 1 0 0 1-1.424 0C9.974 5.137 8.504 4.814 7.266 5.09c-1.263.282-2.379 1.206-2.92 2.556C3.33 10.18 4.252 14.84 12 19.348c7.747-4.508 8.67-9.168 7.654-11.7-.541-1.351-1.657-2.275-2.92-2.557Zm4.777 1.812c1.604 4-.494 9.69-9.022 14.47a1 1 0 0 1-.978 0C2.983 16.592.885 10.902 2.49 6.902c.779-1.942 2.414-3.334 4.342-3.764 1.697-.378 3.552.003 5.169 1.286 1.617-1.283 3.472-1.664 5.17-1.286 1.927.43 3.562 1.822 4.34 3.764Z" clip-rule="evenodd"/></svg> diff --git a/assets/icons/person_stroke2_corner0_rounded.svg b/assets/icons/person_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..a23ad7607 --- /dev/null +++ b/assets/icons/person_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.678 19h12.644c-.71-2.909-3.092-5-6.322-5s-5.613 2.091-6.322 5Zm-2.174.906C3.917 15.521 7.242 12 12 12c4.758 0 8.083 3.521 8.496 7.906A1 1 0 0 1 19.5 21h-15a1 1 0 0 1-.996-1.094Z" clip-rule="evenodd"/></svg> diff --git a/assets/icons/raisingHand4Finger_stroke2_corner0_rounded.svg b/assets/icons/raisingHand4Finger_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..aed3d9e7e --- /dev/null +++ b/assets/icons/raisingHand4Finger_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M10.25 4a.75.75 0 0 0-.75.75V11a1 1 0 1 1-2 0V6.75a.75.75 0 0 0-1.5 0V14a6 6 0 0 0 12 0V9a2 2 0 0 0-2 2v1.5a1 1 0 0 1-.684.949l-.628.21A2.469 2.469 0 0 0 13 16a1 1 0 1 1-2 0 4.469 4.469 0 0 1 3-4.22V11c0-.703.181-1.364.5-1.938V5.75a.75.75 0 0 0-1.5 0V9a1 1 0 1 1-2 0V4.75a.75.75 0 0 0-.75-.75Zm2.316-.733A2.75 2.75 0 0 1 16.5 5.75v1.54c.463-.187.97-.29 1.5-.29h1a1 1 0 0 1 1 1v6a8 8 0 1 1-16 0V6.75a2.75 2.75 0 0 1 3.571-2.625 2.751 2.751 0 0 1 4.995-.858Z" clip-rule="evenodd"/></svg> diff --git a/assets/icons/settingsGear2_stroke2_corner0_rounded.svg b/assets/icons/settingsGear2_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..de8a57919 --- /dev/null +++ b/assets/icons/settingsGear2_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M11.1 2a1 1 0 0 0-.832.445L8.851 4.57 6.6 4.05a1 1 0 0 0-.932.268l-1.35 1.35a1 1 0 0 0-.267.932l.52 2.251-2.126 1.417A1 1 0 0 0 2 11.1v1.8a1 1 0 0 0 .445.832l2.125 1.417-.52 2.251a1 1 0 0 0 .268.932l1.35 1.35a1 1 0 0 0 .932.267l2.251-.52 1.417 2.126A1 1 0 0 0 11.1 22h1.8a1 1 0 0 0 .832-.445l1.417-2.125 2.251.52a1 1 0 0 0 .932-.268l1.35-1.35a1 1 0 0 0 .267-.932l-.52-2.251 2.126-1.417A1 1 0 0 0 22 12.9v-1.8a1 1 0 0 0-.445-.832L19.43 8.851l.52-2.251a1 1 0 0 0-.268-.932l-1.35-1.35a1 1 0 0 0-.932-.267l-2.251.52-1.417-2.126A1 1 0 0 0 12.9 2h-1.8Zm-.968 4.255L11.635 4h.73l1.503 2.255a1 1 0 0 0 1.057.42l2.385-.551.566.566-.55 2.385a1 1 0 0 0 .42 1.057L20 11.635v.73l-2.255 1.503a1 1 0 0 0-.42 1.057l.551 2.385-.566.566-2.385-.55a1 1 0 0 0-1.057.42L12.365 20h-.73l-1.503-2.255a1 1 0 0 0-1.057-.42l-2.385.551-.566-.566.55-2.385a1 1 0 0 0-.42-1.057L4 12.365v-.73l2.255-1.503a1 1 0 0 0 .42-1.057L6.123 6.69l.566-.566 2.385.55a1 1 0 0 0 1.057-.42ZM8 12a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm4-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z" clip-rule="evenodd"/></svg> diff --git a/assets/icons/shield_stroke2_corner0_rounded.svg b/assets/icons/shield_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..c4ef98e5a --- /dev/null +++ b/assets/icons/shield_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M11.675 2.054a1 1 0 0 1 .65 0l8 2.75A1 1 0 0 1 21 5.75v6.162c0 2.807-1.149 4.83-2.813 6.405-1.572 1.488-3.632 2.6-5.555 3.636l-.157.085a1 1 0 0 1-.95 0l-.157-.085c-1.923-1.037-3.983-2.148-5.556-3.636C4.15 16.742 3 14.719 3 11.912V5.75a1 1 0 0 1 .675-.946l8-2.75ZM5 6.464v5.448c0 2.166.851 3.687 2.188 4.952 1.276 1.209 2.964 2.158 4.812 3.157 1.848-1 3.536-1.948 4.813-3.157C18.148 15.6 19 14.078 19 11.912V6.464l-7-2.407-7 2.407Z" clip-rule="evenodd"/></svg> \ No newline at end of file diff --git a/assets/icons/squareArrowTopRight_stroke2_corner0_rounded.svg b/assets/icons/squareArrowTopRight_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..1407a1d6f --- /dev/null +++ b/assets/icons/squareArrowTopRight_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M14 5a1 1 0 1 1 0-2h6a1 1 0 0 1 1 1v6a1 1 0 1 1-2 0V6.414l-7.293 7.293a1 1 0 0 1-1.414-1.414L17.586 5H14ZM3 6a1 1 0 0 1 1-1h5a1 1 0 0 1 0 2H5v12h12v-4a1 1 0 1 1 2 0v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6Z" clip-rule="evenodd"/></svg> diff --git a/assets/icons/squareBehindSquare4_stroke2_corner0_rounded.svg b/assets/icons/squareBehindSquare4_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..3fa7e5d39 --- /dev/null +++ b/assets/icons/squareBehindSquare4_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M8 8V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1h-5v5a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h5Zm1 8a1 1 0 0 1-1-1v-5H4v10h10v-4H9Z" clip-rule="evenodd"/></svg> diff --git a/assets/icons/triangleExclamation_stroke2_corner2_rounded.svg b/assets/icons/triangleExclamation_stroke2_corner2_rounded.svg new file mode 100644 index 000000000..aa5640445 --- /dev/null +++ b/assets/icons/triangleExclamation_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12.86 4.494a.995.995 0 0 0-1.72 0L4.14 16.502A.996.996 0 0 0 4.999 18h14.003a.996.996 0 0 0 .86-1.498L12.86 4.494ZM9.413 3.487c1.155-1.983 4.019-1.983 5.174 0l7.002 12.007C22.753 17.491 21.314 20 19.002 20H4.998c-2.312 0-3.751-2.509-2.587-4.506L9.413 3.487ZM12 8.019a1 1 0 0 1 1 1v2.994a1 1 0 1 1-2 0V9.02a1 1 0 0 1 1-1Z" clip-rule="evenodd"/><rect width="2.5" height="2.5" x="10.75" y="13.75" fill="#000" rx="1.25"/></svg> \ No newline at end of file diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index e159d780a..54a3925c6 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -188,6 +188,7 @@ func serve(cctx *cli.Context) error { e.GET("/settings/threads", server.WebGeneric) e.GET("/settings/external-embeds", server.WebGeneric) e.GET("/sys/debug", server.WebGeneric) + e.GET("/sys/debug-mod", server.WebGeneric) e.GET("/sys/log", server.WebGeneric) e.GET("/support", server.WebGeneric) e.GET("/support/privacy", server.WebGeneric) @@ -203,6 +204,7 @@ func serve(cctx *cli.Context) error { e.GET("/profile/:handleOrDID/lists/:rkey", server.WebGeneric) e.GET("/profile/:handleOrDID/feed/:rkey", server.WebGeneric) e.GET("/profile/:handleOrDID/feed/:rkey/liked-by", server.WebGeneric) + e.GET("/profile/:handleOrDID/labeler/liked-by", server.WebGeneric) // profile RSS feed (DID not handle) e.GET("/profile/:ident/rss", server.WebProfileRSS) diff --git a/bskyweb/static/iframe/youtube.html b/bskyweb/static/iframe/youtube.html index f2ada2ec5..4b74d6fcd 100644 --- a/bskyweb/static/iframe/youtube.html +++ b/bskyweb/static/iframe/youtube.html @@ -5,16 +5,14 @@ } .container { position: relative; - width: 100%; - height: 0; - padding-bottom: 56.25%; + overflow: hidden; + width: 100vw; + height: 100vh; } .video { position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; + width: 100vw; + height: 100vh; } </style> <div class="container"><div class="video" id="player"></div></div> diff --git a/code-signing/certificate.pem b/code-signing/certificate.pem new file mode 100644 index 000000000..bfc5cdbde --- /dev/null +++ b/code-signing/certificate.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC0TCCAbmgAwIBAgIJcMN2yt5KNDqTMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV +BAMTB0JsdWVza3kwHhcNMjQwMzE0MDA1OTU4WhcNMzQwMzE0MDA1OTU4WjASMRAw +DgYDVQQDEwdCbHVlc2t5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +izSAWEc3wRoa3eTBEh/kE9pH0d6jhEGw9GrYfei60MHT1pSq2cTdyUM1yUZchAeW +gFFtqFxX0pfIZQyMlIZbjkaOxOqzWhB0aCsxngnhbSahFwRxkVwTAuonhqIpaLBL +hrCCCQ2IfZUpy8QeasqlTlmvmijuCC34fXxJlxNcj8SqzIZi+civ7U5PMPfIMMnD +tCDIBy1vxMk57m25X2ikcWUFW64qNVLkFAL36xEnmFTL4Ivqpz23gUcUIe1zbesY +jAgDtlwnAE7mU3oagCUDcSuOveT4POhT35Xp3Y/07I68kmXtrPxwd5k0L0zbisEm +poKZ87E2X29BitihicMpBwIDAQABoyowKDAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0l +AQH/BAwwCgYIKwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAED1gdMF0yr8Gy87 +RgyaeVpPySwSsO0selmXXrcmOWgiPA05lubyhFEa4P5kdzBEByG2MT+pJkjGYpvK +XRnqXM5VvdS2RhYYFH0cFOIUqBKwCnzViCMuGQeoGUx4oPcKFS0PQ1WjW2d4pS75 +51GBfB6LOepsCHUG0A9XEk7EAyUWc4M2ITCJsTtJh8CVn2pTks2q14ETDs86YQv4 +peDaJv8nhIe8oQkeGn2o/P/ctkwJg/uBydQUsWgjjGTQZTilVjGTW1mwDr9FucAE +d5gKIk4rtR/3Zd/NDdqp8PrkoWeVM7Hwr789/mpUOeqa/j7YNkDYQh7x+M/odd1D +KY0bQEQ= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/jest/jestSetup.js b/jest/jestSetup.js index d8cee9bfd..e690e813a 100644 --- a/jest/jestSetup.js +++ b/jest/jestSetup.js @@ -88,3 +88,5 @@ jest.mock('sentry-expo', () => ({ ReactNavigationInstrumentation: jest.fn(), }, })) + +jest.mock('crypto', () => ({})) diff --git a/lingui.config.js b/lingui.config.js index 3916df5e6..6da69e98e 100644 --- a/lingui.config.js +++ b/lingui.config.js @@ -4,6 +4,7 @@ module.exports = { 'en', 'de', 'es', + 'fi', 'fr', 'hi', 'id', diff --git a/package.json b/package.json index 896201820..97fd91b03 100644 --- a/package.json +++ b/package.json @@ -41,10 +41,12 @@ "intl:extract": "lingui extract", "intl:compile": "lingui compile", "nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android", - "update-extensions": "scripts/updateExtensions.sh" + "update-extensions": "bash scripts/updateExtensions.sh", + "export": "npx expo export", + "make-deploy-bundle": "bash scripts/bundleUpdate.sh" }, "dependencies": { - "@atproto/api": "^0.10.5", + "@atproto/api": "^0.12.1", "@bam.tech/react-native-image-resizer": "^3.0.4", "@braintree/sanitize-url": "^6.0.2", "@emoji-mart/react": "^1.1.1", @@ -76,7 +78,9 @@ "@segment/sovran-react-native": "^0.4.5", "@sentry/react-native": "5.5.0", "@tamagui/focus-scope": "^1.84.1", + "@tanstack/query-async-storage-persister": "^5.25.0", "@tanstack/react-query": "^5.8.1", + "@tanstack/react-query-persist-client": "^5.25.0", "@tiptap/core": "^2.0.0-beta.220", "@tiptap/extension-document": "^2.0.0-beta.220", "@tiptap/extension-hard-break": "^2.0.3", diff --git a/patches/expo-image-picker+14.7.1.patch b/patches/expo-image-picker+14.7.1.patch index 2d37a182a..046eb4f4f 100644 --- a/patches/expo-image-picker+14.7.1.patch +++ b/patches/expo-image-picker+14.7.1.patch @@ -1,8 +1,56 @@ +diff --git a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt +index 3f50f8c..ee47fa1 100644 +--- a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt ++++ b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt +@@ -33,7 +33,9 @@ import kotlin.coroutines.resumeWithException + // TODO(@bbarthec): rename to ExpoImagePicker + private const val moduleName = "ExponentImagePicker" + ++ + class ImagePickerModule : Module() { ++ private var isPickerOpen = false + + override fun definition() = ModuleDefinition { + Name(moduleName) +@@ -129,6 +131,11 @@ class ImagePickerModule : Module() { + options: ImagePickerOptions + ): Any { + return try { ++ if(isPickerOpen) { ++ return ImagePickerResponse(canceled = true) ++ } ++ ++ isPickerOpen = true + var result = launchPicker(pickerLauncher) + if ( + !options.allowsMultipleSelection && +@@ -143,6 +150,8 @@ class ImagePickerModule : Module() { + mediaHandler.readExtras(result.data, options) + } catch (cause: OperationCanceledException) { + return ImagePickerResponse(canceled = true) ++ } finally { ++ isPickerOpen = false + } + } + diff --git a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt -index ff15c91..41aaf12 100644 +index ff15c91..9763012 100644 --- a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt +++ b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt -@@ -26,51 +26,26 @@ import java.io.Serializable +@@ -5,12 +5,7 @@ import android.content.ContentResolver + import android.content.Context + import android.content.Intent + import android.net.Uri +-import androidx.activity.result.PickVisualMediaRequest +-import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia +-import androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia + import expo.modules.imagepicker.ImagePickerOptions +-import expo.modules.imagepicker.MediaTypes +-import expo.modules.imagepicker.UNLIMITED_SELECTION + import expo.modules.imagepicker.getAllDataUris + import expo.modules.imagepicker.toMediaType + import expo.modules.kotlin.activityresult.AppContextActivityResultContract +@@ -26,51 +21,26 @@ import java.io.Serializable * @see [androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents] */ internal class ImageLibraryContract( @@ -12,7 +60,7 @@ index ff15c91..41aaf12 100644 private val contentResolver: ContentResolver get() = appContextProvider.appContext.reactContext?.contentResolver ?: throw Exceptions.ReactContextLost() - + override fun createIntent(context: Context, input: ImageLibraryContractOptions): Intent { - val request = PickVisualMediaRequest.Builder() - .setMediaType( @@ -34,7 +82,7 @@ index ff15c91..41aaf12 100644 + val intent = Intent(Intent.ACTION_GET_CONTENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType("image/*") - + if (input.options.allowsMultipleSelection) { - val selectionLimit = input.options.selectionLimit - @@ -45,7 +93,7 @@ index ff15c91..41aaf12 100644 + if(input.options.selectionLimit == 1) { + return intent } - + - if (selectionLimit > 1) { - return PickMultipleVisualMedia(selectionLimit).createIntent(context, request) - } @@ -56,9 +104,9 @@ index ff15c91..41aaf12 100644 - } + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) } - + - return PickVisualMedia().createIntent(context, request) + return intent } - + override fun parseResult(input: ImageLibraryContractOptions, resultCode: Int, intent: Intent?) = diff --git a/patches/react-native+0.73.2.patch b/patches/react-native+0.73.2.patch index 8f100169e..8db23da0c 100644 --- a/patches/react-native+0.73.2.patch +++ b/patches/react-native+0.73.2.patch @@ -1,92 +1,19 @@ -diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm -index 9dca6a5..090bda5 100644 ---- a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm -+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm -@@ -266,11 +266,10 @@ - (void)textViewDidChange:(__unused UITextView *)textView +diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m +index b09e653..d290dab 100644 +--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m ++++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m +@@ -198,6 +198,14 @@ - (void)refreshControlValueChanged + [self setCurrentRefreshingState:super.refreshing]; + _refreshingProgrammatically = NO; - - (void)textViewDidChangeSelection:(__unused UITextView *)textView - { -- if (_lastStringStateWasUpdatedWith && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) { -+ if (![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) { - [self textViewDidChange:_backedTextInputView]; - _ignoreNextTextInputCall = YES; - } -- _lastStringStateWasUpdatedWith = _backedTextInputView.attributedText; - [self textViewProbablyDidChangeSelection]; - } - -diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm -index 1f06b79..ab458f3 100644 ---- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm -+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm -@@ -87,7 +87,7 @@ - (void)invalidateContentSize - return; - } - -- CGSize maximumSize = self.layoutMetrics.frame.size; -+ CGSize maximumSize = self.layoutMetrics.contentFrame.size; - - if (_maximumNumberOfLines == 1) { - maximumSize.width = CGFLOAT_MAX; -@@ -158,6 +158,8 @@ - (void)uiManagerWillPerformMounting - [attributedText insertAttributedString:propertyAttributedText atIndex:0]; - } - -+ [self postprocessAttributedText:attributedText]; -+ - NSAttributedString *newAttributedText; - if (![_previousAttributedText isEqualToAttributedString:attributedText]) { - // We have to follow `set prop` pattern: -@@ -191,6 +193,52 @@ - (void)uiManagerWillPerformMounting - }]; - } - -+- (void)postprocessAttributedText:(NSMutableAttributedString *)attributedText -+{ -+ __block CGFloat maximumLineHeight = 0; -+ -+ [attributedText enumerateAttribute:NSParagraphStyleAttributeName -+ inRange:NSMakeRange(0, attributedText.length) -+ options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired -+ usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) { -+ if (!paragraphStyle) { -+ return; ++ if (@available(iOS 17.4, *)) { ++ if (_currentRefreshingState) { ++ UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; ++ [feedbackGenerator prepare]; ++ [feedbackGenerator impactOccurred]; + } -+ -+ maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight); -+ }]; -+ -+ if (maximumLineHeight == 0) { -+ // `lineHeight` was not specified, nothing to do. -+ return; + } + -+ __block CGFloat maximumFontLineHeight = 0; -+ -+ [attributedText enumerateAttribute:NSFontAttributeName -+ inRange:NSMakeRange(0, attributedText.length) -+ options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired -+ usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { -+ if (!font) { -+ return; -+ } -+ -+ if (maximumFontLineHeight <= font.lineHeight) { -+ maximumFontLineHeight = font.lineHeight; -+ } -+ }]; -+ -+ if (maximumLineHeight < maximumFontLineHeight) { -+ return; -+ } -+ -+ CGFloat baseLineOffset = maximumLineHeight / 2.0 - maximumFontLineHeight / 2.0; -+ -+ [attributedText addAttribute:NSBaselineOffsetAttributeName -+ value:@(baseLineOffset) -+ range:NSMakeRange(0, attributedText.length)]; -+} -+ - #pragma mark - - - - (NSAttributedString *)measurableAttributedText + if (_onRefresh) { + _onRefresh(nil); + } \ No newline at end of file diff --git a/patches/react-native+0.73.2.patch.md b/patches/react-native+0.73.2.patch.md index 3d3275163..7f70baf2f 100644 --- a/patches/react-native+0.73.2.patch.md +++ b/patches/react-native+0.73.2.patch.md @@ -1,5 +1,5 @@ -# TextInput Patch +# RefreshControl Patch -Patching `RCTBaseTextShadowInput.mm` from https://github.com/facebook/react-native/pull/38359. This fixes some text -getting cut off inside the composer. This was merged in December, so we should be able to remove this patch when RN -ships the next release. +Patching `RCTRefreshControl.mm` temporarily to play an impact haptic on refresh when using iOS 17.4 or higher. Since +17.4, there has been a regression somewhere causing haptics to not play on iOS on refresh. Should monitor for an update +in the RN repo: https://github.com/facebook/react-native/issues/43388 \ No newline at end of file diff --git a/scripts/bundleUpdate.js b/scripts/bundleUpdate.js new file mode 100644 index 000000000..00217dcd7 --- /dev/null +++ b/scripts/bundleUpdate.js @@ -0,0 +1,104 @@ +const crypto = require('crypto') +const fs = require('fs') +const fsp = fs.promises +const path = require('path') + +const DIST_DIR = './dist' +const BUNDLES_DIR = '/_expo/static/js' +const IOS_BUNDLE_DIR = path.join(DIST_DIR, BUNDLES_DIR, '/ios') +const ANDROID_BUNDLE_DIR = path.join(DIST_DIR, BUNDLES_DIR, '/android') +const METADATA_PATH = path.join(DIST_DIR, '/metadata.json') +const DEST_DIR = './bundleTempDir' + +// Weird, don't feel like figuring out _why_ it wants this +const METADATA = require(`../${METADATA_PATH}`) +const IOS_METADATA_ASSETS = METADATA.fileMetadata.ios.assets +const ANDROID_METADATA_ASSETS = METADATA.fileMetadata.android.assets + +const getMd5 = async path => { + return new Promise(res => { + const hash = crypto.createHash('md5') + const rStream = fs.createReadStream(path) + rStream.on('data', data => { + hash.update(data) + }) + rStream.on('end', () => { + res(hash.digest('hex')) + }) + }) +} + +const moveFiles = async () => { + console.log('Making directory...') + await fsp.mkdir(DEST_DIR) + await fsp.mkdir(path.join(DEST_DIR, '/assets')) + + console.log('Getting ios md5...') + const iosCurrPath = path.join( + IOS_BUNDLE_DIR, + (await fsp.readdir(IOS_BUNDLE_DIR))[0], + ) + const iosMd5 = await getMd5(iosCurrPath) + const iosNewPath = `bundles/${iosMd5}.bundle` + + console.log('Copying ios bundle...') + await fsp.cp(iosCurrPath, path.join(DEST_DIR, iosNewPath)) + + console.log('Getting android md5...') + const androidCurrPath = path.join( + ANDROID_BUNDLE_DIR, + (await fsp.readdir(ANDROID_BUNDLE_DIR))[0], + ) + const androidMd5 = await getMd5(androidCurrPath) + const androidNewPath = `bundles/${androidMd5}.bundle` + + console.log('Copying android bundle...') + await fsp.cp(androidCurrPath, path.join(DEST_DIR, androidNewPath)) + + const iosAssets = [] + const androidAssets = [] + + console.log('Getting ios asset md5s and moving them...') + for (const asset of IOS_METADATA_ASSETS) { + const currPath = path.join(DIST_DIR, asset.path) + const md5 = await getMd5(currPath) + const withExtPath = `assets/${md5}.${asset.ext}` + iosAssets.push(withExtPath) + await fsp.cp(currPath, path.join(DEST_DIR, withExtPath)) + } + + console.log('Getting android asset md5s and moving them...') + for (const asset of ANDROID_METADATA_ASSETS) { + const currPath = path.join(DIST_DIR, asset.path) + const md5 = await getMd5(currPath) + const withExtPath = `assets/${md5}.${asset.ext}` + androidAssets.push(withExtPath) + await fsp.cp(currPath, path.join(DEST_DIR, withExtPath)) + } + + const result = { + version: 0, + bundler: 'metro', + fileMetadata: { + ios: { + bundle: iosNewPath, + assets: iosAssets, + }, + android: { + bundle: androidNewPath, + assets: androidAssets, + }, + }, + } + + console.log('Writing metadata...') + await fsp.writeFile( + path.join(DEST_DIR, 'metadata.json'), + JSON.stringify(result), + ) + + console.log('Finished!') + console.log('Metadata:', result) +} + +moveFiles() diff --git a/scripts/bundleUpdate.sh b/scripts/bundleUpdate.sh new file mode 100644 index 000000000..18db81a20 --- /dev/null +++ b/scripts/bundleUpdate.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -o errexit +set -o pipefail +set -o nounset + +rm -rf bundleTempDir +rm -rf bundle.tar.gz + +echo "Creating tarball..." +node scripts/bundleUpdate.js + +cd bundleTempDir || exit + +BUNDLE_VERSION=$(date +%s) +DEPLOYMENT_URL="https://updates.bsky.app/v1/upload?runtime-version=$RUNTIME_VERSION&bundle-version=$BUNDLE_VERSION" + +tar czvf bundle.tar.gz ./* + +echo "Deploying to $DEPLOYMENT_URL..." + +curl -o - --form "bundle=@./bundle.tar.gz" --user "bsky:$DENIS_API_KEY" --basic "$DEPLOYMENT_URL" + +cd .. + +rm -rf bundleTempDir +rm -rf bundle.tar.gz diff --git a/src/App.native.tsx b/src/App.native.tsx index eff8ab099..e825ffa00 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -5,7 +5,7 @@ import React, {useState, useEffect} from 'react' import {RootSiblingParent} from 'react-native-root-siblings' import * as SplashScreen from 'expo-splash-screen' import {GestureHandlerRootView} from 'react-native-gesture-handler' -import {QueryClientProvider} from '@tanstack/react-query' +import {PersistQueryClientProvider} from '@tanstack/react-query-persist-client' import { SafeAreaProvider, initialWindowMetrics, @@ -22,7 +22,11 @@ import {s} from 'lib/styles' import {Shell} from 'view/shell' import * as notifications from 'lib/notifications/notifications' import * as Toast from 'view/com/util/Toast' -import {queryClient} from 'lib/react-query' +import { + queryClient, + asyncStoragePersister, + dehydrateOptions, +} from 'lib/react-query' import {TestCtrls} from 'view/com/testing/TestCtrls' import {Provider as ShellStateProvider} from 'state/shell' import {Provider as ModalStateProvider} from 'state/modals' @@ -33,6 +37,7 @@ import {Provider as InvitesStateProvider} from 'state/invites' import {Provider as PrefsStateProvider} from 'state/preferences' import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out' import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed' +import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs' import I18nProvider from './locale/i18nProvider' import { Provider as SessionProvider, @@ -79,21 +84,23 @@ function InnerApp() { // Resets the entire tree below when it changes: key={currentAccount?.did}> <StatsigProvider> - <LoggedOutViewProvider> - <SelectedFeedProvider> - <UnreadNotifsProvider> - <ThemeProvider theme={theme}> - {/* All components should be within this provider */} - <RootSiblingParent> - <GestureHandlerRootView style={s.h100pct}> - <TestCtrls /> - <Shell /> - </GestureHandlerRootView> - </RootSiblingParent> - </ThemeProvider> - </UnreadNotifsProvider> - </SelectedFeedProvider> - </LoggedOutViewProvider> + <LabelDefsProvider> + <LoggedOutViewProvider> + <SelectedFeedProvider> + <UnreadNotifsProvider> + <ThemeProvider theme={theme}> + {/* All components should be within this provider */} + <RootSiblingParent> + <GestureHandlerRootView style={s.h100pct}> + <TestCtrls /> + <Shell /> + </GestureHandlerRootView> + </RootSiblingParent> + </ThemeProvider> + </UnreadNotifsProvider> + </SelectedFeedProvider> + </LoggedOutViewProvider> + </LabelDefsProvider> </StatsigProvider> </React.Fragment> </Splash> @@ -118,7 +125,9 @@ function App() { * that is set up in the InnerApp component above. */ return ( - <QueryClientProvider client={queryClient}> + <PersistQueryClientProvider + client={queryClient} + persistOptions={{persister: asyncStoragePersister, dehydrateOptions}}> <SessionProvider> <ShellStateProvider> <PrefsStateProvider> @@ -140,7 +149,7 @@ function App() { </PrefsStateProvider> </ShellStateProvider> </SessionProvider> - </QueryClientProvider> + </PersistQueryClientProvider> ) } diff --git a/src/App.web.tsx b/src/App.web.tsx index eb2e42593..f47f763da 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -1,7 +1,7 @@ import 'lib/sentry' // must be near top import React, {useState, useEffect} from 'react' -import {QueryClientProvider} from '@tanstack/react-query' +import {PersistQueryClientProvider} from '@tanstack/react-query-persist-client' import {SafeAreaProvider} from 'react-native-safe-area-context' import {RootSiblingParent} from 'react-native-root-siblings' @@ -13,7 +13,11 @@ import {init as initPersistedState} from '#/state/persisted' import {Shell} from 'view/shell/index' import {ToastContainer} from 'view/com/util/Toast.web' import {ThemeProvider} from 'lib/ThemeContext' -import {queryClient} from 'lib/react-query' +import { + queryClient, + asyncStoragePersister, + dehydrateOptions, +} from 'lib/react-query' import {Provider as ShellStateProvider} from 'state/shell' import {Provider as ModalStateProvider} from 'state/modals' import {Provider as DialogStateProvider} from 'state/dialogs' @@ -23,6 +27,7 @@ import {Provider as InvitesStateProvider} from 'state/invites' import {Provider as PrefsStateProvider} from 'state/preferences' import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out' import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed' +import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs' import I18nProvider from './locale/i18nProvider' import { Provider as SessionProvider, @@ -56,21 +61,23 @@ function InnerApp() { // Resets the entire tree below when it changes: key={currentAccount?.did}> <StatsigProvider> - <LoggedOutViewProvider> - <SelectedFeedProvider> - <UnreadNotifsProvider> - <ThemeProvider theme={theme}> - {/* All components should be within this provider */} - <RootSiblingParent> - <SafeAreaProvider> - <Shell /> - </SafeAreaProvider> - </RootSiblingParent> - <ToastContainer /> - </ThemeProvider> - </UnreadNotifsProvider> - </SelectedFeedProvider> - </LoggedOutViewProvider> + <LabelDefsProvider> + <LoggedOutViewProvider> + <SelectedFeedProvider> + <UnreadNotifsProvider> + <ThemeProvider theme={theme}> + {/* All components should be within this provider */} + <RootSiblingParent> + <SafeAreaProvider> + <Shell /> + </SafeAreaProvider> + </RootSiblingParent> + <ToastContainer /> + </ThemeProvider> + </UnreadNotifsProvider> + </SelectedFeedProvider> + </LoggedOutViewProvider> + </LabelDefsProvider> </StatsigProvider> </React.Fragment> </Alf> @@ -93,7 +100,9 @@ function App() { * that is set up in the InnerApp component above. */ return ( - <QueryClientProvider client={queryClient}> + <PersistQueryClientProvider + client={queryClient} + persistOptions={{persister: asyncStoragePersister, dehydrateOptions}}> <SessionProvider> <ShellStateProvider> <PrefsStateProvider> @@ -115,7 +124,7 @@ function App() { </PrefsStateProvider> </ShellStateProvider> </SessionProvider> - </QueryClientProvider> + </PersistQueryClientProvider> ) } diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 77706ce34..3d6a15c4e 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -46,7 +46,7 @@ import {SearchScreen} from './view/screens/Search' import {FeedsScreen} from './view/screens/Feeds' import {NotificationsScreen} from './view/screens/Notifications' import {ListsScreen} from './view/screens/Lists' -import {ModerationScreen} from './view/screens/Moderation' +import {ModerationScreen} from '#/screens/Moderation' import {ModerationModlistsScreen} from './view/screens/ModerationModlists' import {NotFoundScreen} from './view/screens/NotFound' import {SettingsScreen} from './view/screens/Settings' @@ -61,6 +61,7 @@ import {PostThreadScreen} from './view/screens/PostThread' import {PostLikedByScreen} from './view/screens/PostLikedBy' import {PostRepostedByScreen} from './view/screens/PostRepostedBy' import {Storybook} from './view/screens/Storybook' +import {DebugModScreen} from './view/screens/DebugMod' import {LogScreen} from './view/screens/Log' import {SupportScreen} from './view/screens/Support' import {PrivacyPolicyScreen} from './view/screens/PrivacyPolicy' @@ -78,6 +79,7 @@ import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStack import {msg} from '@lingui/macro' import {i18n, MessageDescriptor} from '@lingui/core' import HashtagScreen from '#/screens/Hashtag' +import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' import {logEvent, attachRouteToLogEvents} from './lib/statsig/statsig' const navigationRef = createNavigationContainerRef<AllNavigatorParams>() @@ -199,11 +201,21 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { options={{title: title(msg`Liked by`)}} /> <Stack.Screen + name="ProfileLabelerLikedBy" + getComponent={() => ProfileLabelerLikedByScreen} + options={{title: title(msg`Liked by`)}} + /> + <Stack.Screen name="Debug" getComponent={() => Storybook} options={{title: title(msg`Storybook`), requireAuth: true}} /> <Stack.Screen + name="DebugMod" + getComponent={() => DebugModScreen} + options={{title: title(msg`Moderation states`), requireAuth: true}} + /> + <Stack.Screen name="Log" getComponent={() => LogScreen} options={{title: title(msg`Log`), requireAuth: true}} diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index 5088e3aac..0b473ba90 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -50,6 +50,9 @@ export const atoms = { h_full: { height: '100%', }, + h_full_vh: web({ + height: '100vh', + }), /* * Border radius @@ -243,6 +246,9 @@ export const atoms = { font_normal: { fontWeight: tokens.fontWeight.normal, }, + font_semibold: { + fontWeight: '500', + }, font_bold: { fontWeight: tokens.fontWeight.semibold, }, @@ -528,6 +534,10 @@ export const atoms = { /* * Margin */ + mx_auto: { + marginLeft: 'auto', + marginRight: 'auto', + }, m_2xs: { margin: tokens.space._2xs, }, diff --git a/src/alf/index.tsx b/src/alf/index.tsx index 27738e91d..f0a0ede7a 100644 --- a/src/alf/index.tsx +++ b/src/alf/index.tsx @@ -16,6 +16,7 @@ type BreakpointName = keyof typeof breakpoints const breakpoints: { [key: string]: number } = { + gtPhone: 500, gtMobile: 800, gtTablet: 1300, } @@ -26,6 +27,7 @@ function getActiveBreakpoints({width}: {width: number}) { return { active: active[active.length - 1], + gtPhone: active.includes('gtPhone'), gtMobile: active.includes('gtMobile'), gtTablet: active.includes('gtTablet'), } @@ -39,6 +41,7 @@ export const Context = React.createContext<{ theme: themes.Theme breakpoints: { active: BreakpointName | undefined + gtPhone: boolean gtMobile: boolean gtTablet: boolean } @@ -47,6 +50,7 @@ export const Context = React.createContext<{ theme: themes.light, breakpoints: { active: undefined, + gtPhone: false, gtMobile: false, gtTablet: false, }, diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts index b1468f461..4045c831c 100644 --- a/src/alf/tokens.ts +++ b/src/alf/tokens.ts @@ -12,6 +12,9 @@ export const dimScale = generateScale(12, 100) export const color = { trueBlack: '#000000', + temp_purple: 'rgb(105 0 255)', + temp_purple_dark: 'rgb(83 0 202)', + gray_0: `hsl(${BLUE_HUE}, 20%, ${scale[14]}%)`, gray_25: `hsl(${BLUE_HUE}, 20%, ${scale[13]}%)`, gray_50: `hsl(${BLUE_HUE}, 20%, ${scale[12]}%)`, diff --git a/src/components/Button.tsx b/src/components/Button.tsx index d3bf73cc3..0e22944a3 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -15,6 +15,7 @@ import LinearGradient from 'react-native-linear-gradient' import {useTheme, atoms as a, tokens, android, flatten} from '#/alf' import {Props as SVGIconProps} from '#/components/icons/common' +import {normalizeTextStyles} from '#/components/Typography' export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient' export type ButtonColor = @@ -139,7 +140,7 @@ export function Button({ })) }, [setState]) - const {baseStyles, hoverStyles, focusStyles} = React.useMemo(() => { + const {baseStyles, hoverStyles} = React.useMemo(() => { const baseStyles: ViewStyle[] = [] const hoverStyles: ViewStyle[] = [] const light = t.name === 'light' @@ -191,14 +192,14 @@ export function Button({ if (variant === 'solid') { if (!disabled) { baseStyles.push({ - backgroundColor: t.palette.contrast_50, + backgroundColor: t.palette.contrast_25, }) hoverStyles.push({ - backgroundColor: t.palette.contrast_100, + backgroundColor: t.palette.contrast_50, }) } else { baseStyles.push({ - backgroundColor: t.palette.contrast_200, + backgroundColor: t.palette.contrast_100, }) } } else if (variant === 'outline') { @@ -308,12 +309,6 @@ export function Button({ return { baseStyles, hoverStyles, - focusStyles: [ - ...hoverStyles, - { - outline: 0, - } as ViewStyle, - ], } }, [t, variant, color, size, shape, disabled]) @@ -376,10 +371,8 @@ export function Button({ a.flex_row, a.align_center, a.justify_center, - a.justify_center, flattenedBaseStyles, ...(state.hovered || state.pressed ? hoverStyles : []), - ...(state.focused ? focusStyles : []), flatten(style), ]} onPressIn={onPressIn} @@ -398,7 +391,7 @@ export function Button({ ]}> <LinearGradient colors={ - state.hovered || state.pressed || state.focused + state.hovered || state.pressed ? gradientHoverColors : gradientColors } @@ -527,7 +520,14 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) { const textStyles = useSharedButtonTextStyles() return ( - <Text {...rest} style={[a.font_bold, a.text_center, textStyles, style]}> + <Text + {...rest} + style={normalizeTextStyles([ + a.font_bold, + a.text_center, + textStyles, + style, + ])}> {children} </Text> ) diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx index 3a7f73342..038f6295a 100644 --- a/src/components/Dialog/index.web.tsx +++ b/src/components/Dialog/index.web.tsx @@ -99,7 +99,7 @@ export function Outer({ style={[ web(a.fixed), a.inset_0, - {opacity: 0.5, backgroundColor: t.palette.black}, + {opacity: 0.8, backgroundColor: t.palette.black}, ]} /> )} diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts index 9e7ad3c04..b1a46f853 100644 --- a/src/components/Dialog/types.ts +++ b/src/components/Dialog/types.ts @@ -1,5 +1,5 @@ import React from 'react' -import type {AccessibilityProps} from 'react-native' +import type {AccessibilityProps, GestureResponderEvent} from 'react-native' import {BottomSheetProps} from '@gorhom/bottom-sheet' import {ViewStyleProp} from '#/alf' @@ -10,9 +10,15 @@ type A11yProps = Required<AccessibilityProps> * Mutated by useImperativeHandle to provide a public API for controlling the * dialog. The methods here will actually become the handlers defined within * the `Dialog.Outer` component. + * + * `Partial<GestureResponderEvent>` here allows us to add this directly to the + * `onPress` prop of a button, for example. If this type was not added, we + * would need to create a function to wrap `.open()` with. */ export type DialogControlRefProps = { - open: (options?: DialogControlOpenOptions) => void + open: ( + options?: DialogControlOpenOptions & Partial<GestureResponderEvent>, + ) => void close: (callback?: () => void) => void } diff --git a/src/components/Error.tsx b/src/components/Error.tsx new file mode 100644 index 000000000..1dbf68284 --- /dev/null +++ b/src/components/Error.tsx @@ -0,0 +1,90 @@ +import React from 'react' + +import {CenteredView} from 'view/com/util/Views' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {Text} from '#/components/Typography' +import {View} from 'react-native' +import {Button} from '#/components/Button' +import {useNavigation} from '@react-navigation/core' +import {NavigationProp} from 'lib/routes/types' +import {StackActions} from '@react-navigation/native' +import {router} from '#/routes' + +export function Error({ + title, + message, + onRetry, +}: { + title?: string + message?: string + onRetry?: () => unknown +}) { + const navigation = useNavigation<NavigationProp>() + const t = useTheme() + const {gtMobile} = useBreakpoints() + + const canGoBack = navigation.canGoBack() + const onGoBack = React.useCallback(() => { + if (canGoBack) { + navigation.goBack() + } else { + navigation.navigate('HomeTab') + + // Checking the state for routes ensures that web doesn't encounter errors while going back + if (navigation.getState()?.routes) { + navigation.dispatch(StackActions.push(...router.matchPath('/'))) + } else { + navigation.navigate('HomeTab') + navigation.dispatch(StackActions.popToTop()) + } + } + }, [navigation, canGoBack]) + + return ( + <CenteredView + style={[ + a.flex_1, + a.align_center, + !gtMobile ? a.justify_between : a.gap_5xl, + t.atoms.border_contrast_low, + {paddingTop: 175, paddingBottom: 110}, + ]} + sideBorders> + <View style={[a.w_full, a.align_center, a.gap_lg]}> + <Text style={[a.font_bold, a.text_3xl]}>{title}</Text> + <Text + style={[ + a.text_md, + a.text_center, + t.atoms.text_contrast_high, + {lineHeight: 1.4}, + gtMobile && {width: 450}, + ]}> + {message} + </Text> + </View> + <View style={[a.gap_md, gtMobile ? {width: 350} : [a.w_full, a.px_lg]]}> + {onRetry && ( + <Button + variant="solid" + color="primary" + label="Click here" + onPress={onRetry} + size="large" + style={[a.rounded_sm, a.overflow_hidden, {paddingVertical: 10}]}> + Retry + </Button> + )} + <Button + variant="solid" + color={onRetry ? 'secondary' : 'primary'} + label="Click here" + onPress={onGoBack} + size="large" + style={[a.rounded_sm, a.overflow_hidden, {paddingVertical: 10}]}> + Go Back + </Button> + </View> + </CenteredView> + ) +} diff --git a/src/components/GradientFill.tsx b/src/components/GradientFill.tsx new file mode 100644 index 000000000..dc14aa72b --- /dev/null +++ b/src/components/GradientFill.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import LinearGradient from 'react-native-linear-gradient' + +import {atoms as a, tokens} from '#/alf' + +export function GradientFill({ + gradient, +}: { + gradient: + | typeof tokens.gradients.sky + | typeof tokens.gradients.midnight + | typeof tokens.gradients.sunrise + | typeof tokens.gradients.sunset + | typeof tokens.gradients.bonfire + | typeof tokens.gradients.summer + | typeof tokens.gradients.nordic +}) { + return ( + <LinearGradient + colors={gradient.values.map(c => c[1])} + locations={gradient.values.map(c => c[0])} + start={{x: 0, y: 0}} + end={{x: 1, y: 1}} + style={[a.absolute, a.inset_0]} + /> + ) +} diff --git a/src/components/LabelingServiceCard/index.tsx b/src/components/LabelingServiceCard/index.tsx new file mode 100644 index 000000000..f924f0f59 --- /dev/null +++ b/src/components/LabelingServiceCard/index.tsx @@ -0,0 +1,182 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {AppBskyLabelerDefs} from '@atproto/api' + +import {getLabelingServiceTitle} from '#/lib/moderation' +import {Link as InternalLink, LinkProps} from '#/components/Link' +import {Text} from '#/components/Typography' +import {useLabelerInfoQuery} from '#/state/queries/labeler' +import {atoms as a, useTheme, ViewStyleProp} from '#/alf' +import {RichText} from '#/components/RichText' +import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {sanitizeHandle} from '#/lib/strings/handles' +import {pluralize} from '#/lib/strings/helpers' + +type LabelingServiceProps = { + labeler: AppBskyLabelerDefs.LabelerViewDetailed +} + +export function Outer({ + children, + style, +}: React.PropsWithChildren<ViewStyleProp>) { + return ( + <View + style={[ + a.flex_row, + a.gap_md, + a.w_full, + a.p_lg, + a.pr_md, + a.overflow_hidden, + style, + ]}> + {children} + </View> + ) +} + +export function Avatar({avatar}: {avatar?: string}) { + return <UserAvatar type="labeler" size={40} avatar={avatar} /> +} + +export function Title({value}: {value: string}) { + return <Text style={[a.text_md, a.font_bold]}>{value}</Text> +} + +export function Description({value, handle}: {value?: string; handle: string}) { + return value ? ( + <Text numberOfLines={2}> + <RichText value={value} style={[]} /> + </Text> + ) : ( + <Text> + <Trans>By {sanitizeHandle(handle, '@')}</Trans> + </Text> + ) +} + +export function LikeCount({count}: {count: number}) { + const t = useTheme() + return ( + <Text + style={[ + a.mt_sm, + a.text_sm, + t.atoms.text_contrast_medium, + {fontWeight: '500'}, + ]}> + <Trans> + Liked by {count} {pluralize(count, 'user')} + </Trans> + </Text> + ) +} + +export function Content({children}: React.PropsWithChildren<{}>) { + const t = useTheme() + + return ( + <View + style={[ + a.flex_1, + a.flex_row, + a.gap_md, + a.align_center, + a.justify_between, + ]}> + <View style={[a.gap_xs, a.flex_1]}>{children}</View> + + <ChevronRight size="md" style={[a.z_10, t.atoms.text_contrast_low]} /> + </View> + ) +} + +/** + * The canonical view for a labeling service. Use this or compose your own. + */ +export function Default({ + labeler, + style, +}: LabelingServiceProps & ViewStyleProp) { + return ( + <Outer style={style}> + <Avatar avatar={labeler.creator.avatar} /> + <Content> + <Title + value={getLabelingServiceTitle({ + displayName: labeler.creator.displayName, + handle: labeler.creator.handle, + })} + /> + <Description + value={labeler.creator.description} + handle={labeler.creator.handle} + /> + {labeler.likeCount ? <LikeCount count={labeler.likeCount} /> : null} + </Content> + </Outer> + ) +} + +export function Link({ + children, + labeler, +}: LabelingServiceProps & Pick<LinkProps, 'children'>) { + const {_} = useLingui() + + return ( + <InternalLink + to={{ + screen: 'Profile', + params: { + name: labeler.creator.handle, + }, + }} + label={_( + msg`View the labeling service provided by @${labeler.creator.handle}`, + )}> + {children} + </InternalLink> + ) +} + +// TODO not finished yet +export function DefaultSkeleton() { + return ( + <View> + <Text>Loading</Text> + </View> + ) +} + +export function Loader({ + did, + loading: LoadingComponent = DefaultSkeleton, + error: ErrorComponent, + component: Component, +}: { + did: string + loading?: React.ComponentType<{}> + error?: React.ComponentType<{error: string}> + component: React.ComponentType<{ + labeler: AppBskyLabelerDefs.LabelerViewDetailed + }> +}) { + const {isLoading, data, error} = useLabelerInfoQuery({did}) + + return isLoading ? ( + LoadingComponent ? ( + <LoadingComponent /> + ) : null + ) : error || !data ? ( + ErrorComponent ? ( + <ErrorComponent error={error?.message || 'Unknown error'} /> + ) : null + ) : ( + <Component labeler={data} /> + ) +} diff --git a/src/components/LikedByList.tsx b/src/components/LikedByList.tsx new file mode 100644 index 000000000..bd1213639 --- /dev/null +++ b/src/components/LikedByList.tsx @@ -0,0 +1,109 @@ +import React from 'react' +import {View} from 'react-native' +import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' +import {Trans} from '@lingui/macro' + +import {logger} from '#/logger' +import {List} from '#/view/com/util/List' +import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' +import {useResolveUriQuery} from '#/state/queries/resolve-uri' +import {useLikedByQuery} from '#/state/queries/post-liked-by' +import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' +import {ListFooter} from '#/components/Lists' + +import {atoms as a, useTheme} from '#/alf' +import {Loader} from '#/components/Loader' +import {Text} from '#/components/Typography' + +export function LikedByList({uri}: {uri: string}) { + const t = useTheme() + const [isPTRing, setIsPTRing] = React.useState(false) + const { + data: resolvedUri, + error: resolveError, + isFetching: isFetchingResolvedUri, + } = useResolveUriQuery(uri) + const { + data, + isFetching, + isFetched, + isRefetching, + hasNextPage, + fetchNextPage, + isError, + error: likedByError, + refetch, + } = useLikedByQuery(resolvedUri?.uri) + const likes = React.useMemo(() => { + if (data?.pages) { + return data.pages.flatMap(page => page.likes) + } + return [] + }, [data]) + const initialNumToRender = useInitialNumToRender() + const error = resolveError || likedByError + + const onRefresh = React.useCallback(async () => { + setIsPTRing(true) + try { + await refetch() + } catch (err) { + logger.error('Failed to refresh likes', {message: err}) + } + setIsPTRing(false) + }, [refetch, setIsPTRing]) + + const onEndReached = React.useCallback(async () => { + if (isFetching || !hasNextPage || isError) return + try { + await fetchNextPage() + } catch (err) { + logger.error('Failed to load more likes', {message: err}) + } + }, [isFetching, hasNextPage, isError, fetchNextPage]) + + const renderItem = React.useCallback(({item}: {item: GetLikes.Like}) => { + return ( + <ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} /> + ) + }, []) + + if (isFetchingResolvedUri || !isFetched) { + return ( + <View style={[a.w_full, a.align_center, a.p_lg]}> + <Loader size="xl" /> + </View> + ) + } + + return likes.length ? ( + <List + data={likes} + keyExtractor={item => item.actor.did} + refreshing={isPTRing} + onRefresh={onRefresh} + onEndReached={onEndReached} + onEndReachedThreshold={3} + renderItem={renderItem} + initialNumToRender={initialNumToRender} + ListFooterComponent={() => ( + <ListFooter + isFetching={isFetching && !isRefetching} + isError={isError} + error={error ? error.toString() : undefined} + onRetry={fetchNextPage} + /> + )} + /> + ) : ( + <View style={[a.p_lg]}> + <View style={[a.p_lg, a.rounded_sm, t.atoms.bg_contrast_25]}> + <Text style={[a.text_md, a.leading_snug]}> + <Trans> + Nobody has liked this yet. Maybe you should be the first! + </Trans> + </Text> + </View> + </View> + ) +} diff --git a/src/components/LikesDialog.tsx b/src/components/LikesDialog.tsx new file mode 100644 index 000000000..94a3f27e2 --- /dev/null +++ b/src/components/LikesDialog.tsx @@ -0,0 +1,131 @@ +import React, {useMemo, useCallback} from 'react' +import {ActivityIndicator, FlatList, View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' + +import {useResolveUriQuery} from '#/state/queries/resolve-uri' +import {useLikedByQuery} from '#/state/queries/post-liked-by' +import {cleanError} from '#/lib/strings/errors' +import {logger} from '#/logger' + +import {atoms as a, useTheme} from '#/alf' +import {Text} from '#/components/Typography' +import * as Dialog from '#/components/Dialog' +import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' +import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' +import {Loader} from '#/components/Loader' + +interface LikesDialogProps { + control: Dialog.DialogOuterProps['control'] + uri: string +} + +export function LikesDialog(props: LikesDialogProps) { + return ( + <Dialog.Outer control={props.control}> + <Dialog.Handle /> + + <LikesDialogInner {...props} /> + </Dialog.Outer> + ) +} + +export function LikesDialogInner({control, uri}: LikesDialogProps) { + const {_} = useLingui() + const t = useTheme() + + const { + data: resolvedUri, + error: resolveError, + isFetched: hasFetchedResolvedUri, + } = useResolveUriQuery(uri) + const { + data, + isFetching: isFetchingLikedBy, + isFetched: hasFetchedLikedBy, + isFetchingNextPage, + hasNextPage, + fetchNextPage, + isError, + error: likedByError, + } = useLikedByQuery(resolvedUri?.uri) + + const isLoading = !hasFetchedResolvedUri || !hasFetchedLikedBy + const likes = useMemo(() => { + if (data?.pages) { + return data.pages.flatMap(page => page.likes) + } + return [] + }, [data]) + + const onEndReached = useCallback(async () => { + if (isFetchingLikedBy || !hasNextPage || isError) return + try { + await fetchNextPage() + } catch (err) { + logger.error('Failed to load more likes', {message: err}) + } + }, [isFetchingLikedBy, hasNextPage, isError, fetchNextPage]) + + const renderItem = useCallback( + ({item}: {item: GetLikes.Like}) => { + return ( + <ProfileCardWithFollowBtn + key={item.actor.did} + profile={item.actor} + onPress={() => control.close()} + /> + ) + }, + [control], + ) + + return ( + <Dialog.Inner label={_(msg`Users that have liked this content or profile`)}> + <Text style={[a.text_2xl, a.font_bold, a.leading_tight, a.pb_lg]}> + <Trans>Liked by</Trans> + </Text> + + {isLoading ? ( + <View style={{minHeight: 300}}> + <Loader size="xl" /> + </View> + ) : resolveError || likedByError || !data ? ( + <ErrorMessage message={cleanError(resolveError || likedByError)} /> + ) : likes.length === 0 ? ( + <View style={[t.atoms.bg_contrast_50, a.px_md, a.py_xl, a.rounded_md]}> + <Text style={[a.text_center]}> + <Trans> + Nobody has liked this yet. Maybe you should be the first! + </Trans> + </Text> + </View> + ) : ( + <FlatList + data={likes} + keyExtractor={item => item.actor.did} + onEndReached={onEndReached} + renderItem={renderItem} + initialNumToRender={15} + ListFooterComponent={ + <ListFooterComponent isFetching={isFetchingNextPage} /> + } + /> + )} + + <Dialog.Close /> + </Dialog.Inner> + ) +} + +function ListFooterComponent({isFetching}: {isFetching: boolean}) { + if (isFetching) { + return ( + <View style={a.pt_lg}> + <ActivityIndicator /> + </View> + ) + } + return null +} diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 00e6a56f4..7d0e83332 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -251,7 +251,7 @@ export function InlineLink({ onIn: onPressIn, onOut: onPressOut, } = useInteractionState() - const flattenedStyle = flatten(style) + const flattenedStyle = flatten(style) || {} return ( <Text diff --git a/src/components/Lists.tsx b/src/components/Lists.tsx index bb0d24797..d3e072028 100644 --- a/src/components/Lists.tsx +++ b/src/components/Lists.tsx @@ -1,26 +1,28 @@ import React from 'react' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {View} from 'react-native' +import {useLingui} from '@lingui/react' +import {Trans, msg} from '@lingui/macro' + import {CenteredView} from 'view/com/util/Views' import {Loader} from '#/components/Loader' -import {Trans} from '@lingui/macro' import {cleanError} from 'lib/strings/errors' import {Button} from '#/components/Button' import {Text} from '#/components/Typography' -import {StackActions} from '@react-navigation/native' -import {router} from '#/routes' -import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' +import {Error} from '#/components/Error' export function ListFooter({ isFetching, isError, error, onRetry, + height, }: { - isFetching: boolean - isError: boolean + isFetching?: boolean + isError?: boolean error?: string onRetry?: () => Promise<unknown> + height?: number }) { const t = useTheme() @@ -29,11 +31,10 @@ export function ListFooter({ style={[ a.w_full, a.align_center, - a.justify_center, a.border_t, a.pb_lg, t.atoms.border_contrast_low, - {height: 100}, + {height: height ?? 180, paddingTop: 30}, ]}> {isFetching ? ( <Loader size="xl" /> @@ -53,11 +54,12 @@ function ListFooterMaybeError({ error, onRetry, }: { - isError: boolean + isError?: boolean error?: string onRetry?: () => Promise<unknown> }) { const t = useTheme() + const {_} = useLingui() if (!isError) return null @@ -83,7 +85,7 @@ function ListFooterMaybeError({ </Text> <Button variant="gradient" - label="Press to retry" + label={_(msg`Press to retry`)} style={[ a.align_center, a.justify_center, @@ -93,7 +95,7 @@ function ListFooterMaybeError({ a.py_sm, ]} onPress={onRetry}> - Retry + <Trans>Retry</Trans> </Button> </View> </View> @@ -128,121 +130,71 @@ export function ListMaybePlaceholder({ isLoading, isEmpty, isError, - empty, - error, - notFoundType = 'page', + emptyTitle, + emptyMessage, + errorTitle, + errorMessage, + emptyType = 'page', onRetry, }: { isLoading: boolean - isEmpty: boolean - isError: boolean - empty?: string - error?: string - notFoundType?: 'page' | 'results' + isEmpty?: boolean + isError?: boolean + emptyTitle?: string + emptyMessage?: string + errorTitle?: string + errorMessage?: string + emptyType?: 'page' | 'results' onRetry?: () => Promise<unknown> }) { - const navigation = useNavigationDeduped() const t = useTheme() + const {_} = useLingui() const {gtMobile, gtTablet} = useBreakpoints() - const canGoBack = navigation.canGoBack() - const onGoBack = React.useCallback(() => { - if (canGoBack) { - navigation.goBack() - } else { - navigation.navigate('HomeTab') - - // Checking the state for routes ensures that web doesn't encounter errors while going back - if (navigation.getState()?.routes) { - navigation.dispatch(StackActions.push(...router.matchPath('/'))) - } else { - navigation.navigate('HomeTab') - navigation.dispatch(StackActions.popToTop()) - } - } - }, [navigation, canGoBack]) + if (!isLoading && isError) { + return ( + <Error + title={errorTitle ?? _(msg`Oops!`)} + message={errorMessage ?? _(`Something went wrong!`)} + onRetry={onRetry} + /> + ) + } - if (!isEmpty) return null - - return ( - <CenteredView - style={[ - a.flex_1, - a.align_center, - !gtMobile ? a.justify_between : a.gap_5xl, - t.atoms.border_contrast_low, - {paddingTop: 175, paddingBottom: 110}, - ]} - sideBorders={gtMobile} - topBorder={!gtTablet}> - {isLoading ? ( + if (isLoading) { + return ( + <CenteredView + style={[ + a.flex_1, + a.align_center, + !gtMobile ? a.justify_between : a.gap_5xl, + t.atoms.border_contrast_low, + {paddingTop: 175, paddingBottom: 110}, + ]} + sideBorders={gtMobile} + topBorder={!gtTablet}> <View style={[a.w_full, a.align_center, {top: 100}]}> <Loader size="xl" /> </View> - ) : ( - <> - <View style={[a.w_full, a.align_center, a.gap_lg]}> - <Text style={[a.font_bold, a.text_3xl]}> - {isError ? ( - <Trans>Oops!</Trans> - ) : isEmpty ? ( - <> - {notFoundType === 'results' ? ( - <Trans>No results found</Trans> - ) : ( - <Trans>Page not found</Trans> - )} - </> - ) : undefined} - </Text> + </CenteredView> + ) + } - {isError ? ( - <Text - style={[a.text_md, a.text_center, t.atoms.text_contrast_high]}> - {error ? error : <Trans>Something went wrong!</Trans>} - </Text> - ) : isEmpty ? ( - <Text - style={[a.text_md, a.text_center, t.atoms.text_contrast_high]}> - {empty ? ( - empty - ) : ( - <Trans> - We're sorry! We can't find the page you were looking for. - </Trans> - )} - </Text> - ) : undefined} - </View> - <View - style={[a.gap_md, !gtMobile ? [a.w_full, a.px_lg] : {width: 350}]}> - {isError && onRetry && ( - <Button - variant="solid" - color="primary" - label="Click here" - onPress={onRetry} - size="large" - style={[ - a.rounded_sm, - a.overflow_hidden, - {paddingVertical: 10}, - ]}> - Retry - </Button> - )} - <Button - variant="solid" - color={isError && onRetry ? 'secondary' : 'primary'} - label="Click here" - onPress={onGoBack} - size="large" - style={[a.rounded_sm, a.overflow_hidden, {paddingVertical: 10}]}> - Go Back - </Button> - </View> - </> - )} - </CenteredView> - ) + if (isEmpty) { + return ( + <Error + title={ + emptyTitle ?? + (emptyType === 'results' + ? _(msg`No results found`) + : _(msg`Page not found`)) + } + message={ + emptyMessage ?? + _(msg`We're sorry! We can't find the page you were looking for.`) + } + onRetry={onRetry} + /> + ) + } } diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index 9be9dd86b..051e95b95 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -17,7 +17,7 @@ import { ItemIconProps, } from '#/components/Menu/types' import {Button, ButtonText} from '#/components/Button' -import {msg} from '@lingui/macro' +import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {isNative} from 'platform/detection' @@ -209,7 +209,9 @@ function Cancel() { variant="ghost" color="secondary" onPress={() => control.close()}> - <ButtonText>Cancel</ButtonText> + <ButtonText> + <Trans>Cancel</Trans> + </ButtonText> </Button> ) } diff --git a/src/components/Menu/index.web.tsx b/src/components/Menu/index.web.tsx index f4b03f680..60b234203 100644 --- a/src/components/Menu/index.web.tsx +++ b/src/components/Menu/index.web.tsx @@ -223,7 +223,7 @@ export function Item({children, label, onPress, ...rest}: ItemProps) { style={flatten([ a.flex_row, a.align_center, - a.gap_sm, + a.gap_lg, a.py_sm, a.rounded_xs, {minHeight: 32, paddingHorizontal: 10}, diff --git a/src/components/ReportDialog/SelectLabelerView.tsx b/src/components/ReportDialog/SelectLabelerView.tsx new file mode 100644 index 000000000..817426355 --- /dev/null +++ b/src/components/ReportDialog/SelectLabelerView.tsx @@ -0,0 +1,92 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {AppBskyLabelerDefs} from '@atproto/api' + +export {useDialogControl as useReportDialogControl} from '#/components/Dialog' +import {getLabelingServiceTitle} from '#/lib/moderation' + +import {atoms as a, useTheme, useBreakpoints} from '#/alf' +import {Text} from '#/components/Typography' +import {Button, useButtonContext} from '#/components/Button' +import {Divider} from '#/components/Divider' +import * as LabelingServiceCard from '#/components/LabelingServiceCard' + +import {ReportDialogProps} from './types' + +export function SelectLabelerView({ + ...props +}: ReportDialogProps & { + labelers: AppBskyLabelerDefs.LabelerViewDetailed[] + onSelectLabeler: (v: string) => void +}) { + const t = useTheme() + const {_} = useLingui() + const {gtMobile} = useBreakpoints() + + return ( + <View style={[a.gap_lg]}> + <View style={[a.justify_center, gtMobile ? a.gap_sm : a.gap_xs]}> + <Text style={[a.text_2xl, a.font_bold]}> + <Trans>Select moderator</Trans> + </Text> + <Text style={[a.text_md, t.atoms.text_contrast_medium]}> + <Trans>To whom would you like to send this report?</Trans> + </Text> + </View> + + <Divider /> + + <View style={[a.gap_xs, {marginHorizontal: a.p_md.padding * -1}]}> + {props.labelers.map(labeler => { + return ( + <Button + key={labeler.creator.did} + label={_(msg`Send report to ${labeler.creator.displayName}`)} + onPress={() => props.onSelectLabeler(labeler.creator.did)}> + <LabelerButton labeler={labeler} /> + </Button> + ) + })} + </View> + </View> + ) +} + +function LabelerButton({ + labeler, +}: { + labeler: AppBskyLabelerDefs.LabelerViewDetailed +}) { + const t = useTheme() + const {hovered, pressed} = useButtonContext() + const interacted = hovered || pressed + + const styles = React.useMemo(() => { + return { + interacted: { + backgroundColor: t.palette.contrast_50, + }, + } + }, [t]) + + return ( + <LabelingServiceCard.Outer + style={[a.p_md, a.rounded_sm, interacted && styles.interacted]}> + <LabelingServiceCard.Avatar avatar={labeler.creator.avatar} /> + <LabelingServiceCard.Content> + <LabelingServiceCard.Title + value={getLabelingServiceTitle({ + displayName: labeler.creator.displayName, + handle: labeler.creator.handle, + })} + /> + <Text + style={[t.atoms.text_contrast_medium, a.text_sm, a.font_semibold]}> + @{labeler.creator.handle} + </Text> + </LabelingServiceCard.Content> + </LabelingServiceCard.Outer> + ) +} diff --git a/src/components/ReportDialog/SelectReportOptionView.tsx b/src/components/ReportDialog/SelectReportOptionView.tsx new file mode 100644 index 000000000..bacf5a867 --- /dev/null +++ b/src/components/ReportDialog/SelectReportOptionView.tsx @@ -0,0 +1,200 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {AppBskyLabelerDefs} from '@atproto/api' + +import {useReportOptions, ReportOption} from '#/lib/moderation/useReportOptions' +import {DMCA_LINK} from '#/components/ReportDialog/const' +import {Link} from '#/components/Link' +export {useDialogControl as useReportDialogControl} from '#/components/Dialog' + +import {atoms as a, useTheme, useBreakpoints} from '#/alf' +import {Text} from '#/components/Typography' +import { + Button, + ButtonIcon, + ButtonText, + useButtonContext, +} from '#/components/Button' +import {Divider} from '#/components/Divider' +import { + ChevronRight_Stroke2_Corner0_Rounded as ChevronRight, + ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft, +} from '#/components/icons/Chevron' +import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRight} from '#/components/icons/SquareArrowTopRight' + +import {ReportDialogProps} from './types' + +export function SelectReportOptionView({ + ...props +}: ReportDialogProps & { + labelers: AppBskyLabelerDefs.LabelerViewDetailed[] + onSelectReportOption: (reportOption: ReportOption) => void + goBack: () => void +}) { + const t = useTheme() + const {_} = useLingui() + const {gtMobile} = useBreakpoints() + const allReportOptions = useReportOptions() + const reportOptions = allReportOptions[props.params.type] + + const i18n = React.useMemo(() => { + let title = _(msg`Report this content`) + let description = _(msg`Why should this content be reviewed?`) + + if (props.params.type === 'account') { + title = _(msg`Report this user`) + description = _(msg`Why should this user be reviewed?`) + } else if (props.params.type === 'post') { + title = _(msg`Report this post`) + description = _(msg`Why should this post be reviewed?`) + } else if (props.params.type === 'list') { + title = _(msg`Report this list`) + description = _(msg`Why should this list be reviewed?`) + } else if (props.params.type === 'feedgen') { + title = _(msg`Report this feed`) + description = _(msg`Why should this feed be reviewed?`) + } + + return { + title, + description, + } + }, [_, props.params.type]) + + return ( + <View style={[a.gap_lg]}> + {props.labelers?.length > 1 ? ( + <Button + size="small" + variant="solid" + color="secondary" + shape="round" + label={_(msg`Go back to previous step`)} + onPress={props.goBack}> + <ButtonIcon icon={ChevronLeft} /> + </Button> + ) : null} + + <View style={[a.justify_center, gtMobile ? a.gap_sm : a.gap_xs]}> + <Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text> + <Text style={[a.text_md, t.atoms.text_contrast_medium]}> + {i18n.description} + </Text> + </View> + + <Divider /> + + <View style={[a.gap_sm, {marginHorizontal: a.p_md.padding * -1}]}> + {reportOptions.map(reportOption => { + return ( + <Button + key={reportOption.reason} + label={_(msg`Create report for ${reportOption.title}`)} + onPress={() => props.onSelectReportOption(reportOption)}> + <ReportOptionButton + title={reportOption.title} + description={reportOption.description} + /> + </Button> + ) + })} + + {(props.params.type === 'post' || props.params.type === 'account') && ( + <View style={[a.pt_md, a.px_md]}> + <View + style={[ + a.flex_row, + a.align_center, + a.justify_between, + a.gap_lg, + a.p_md, + a.pl_lg, + a.rounded_md, + t.atoms.bg_contrast_900, + ]}> + <Text + style={[ + a.flex_1, + t.atoms.text_inverted, + a.italic, + a.leading_snug, + ]}> + <Trans>Need to report a copyright violation?</Trans> + </Text> + <Link + to={DMCA_LINK} + label={_(msg`View details for reporting a copyright violation`)} + size="small" + variant="solid" + color="secondary"> + <ButtonText> + <Trans>View details</Trans> + </ButtonText> + <ButtonIcon position="right" icon={SquareArrowTopRight} /> + </Link> + </View> + </View> + )} + </View> + </View> + ) +} + +function ReportOptionButton({ + title, + description, +}: { + title: string + description: string +}) { + const t = useTheme() + const {hovered, pressed} = useButtonContext() + const interacted = hovered || pressed + + const styles = React.useMemo(() => { + return { + interacted: { + backgroundColor: t.palette.contrast_50, + }, + } + }, [t]) + + return ( + <View + style={[ + a.w_full, + a.flex_row, + a.align_center, + a.justify_between, + a.p_md, + a.rounded_md, + {paddingRight: 70}, + interacted && styles.interacted, + ]}> + <View style={[a.flex_1, a.gap_xs]}> + <Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}> + {title} + </Text> + <Text style={[a.leading_tight, {maxWidth: 400}]}>{description}</Text> + </View> + + <View + style={[ + a.absolute, + a.inset_0, + a.justify_center, + a.pr_md, + {left: 'auto'}, + ]}> + <ChevronRight + size="md" + fill={ + hovered ? t.palette.primary_500 : t.atoms.text_contrast_low.color + } + /> + </View> + </View> + ) +} diff --git a/src/components/ReportDialog/SubmitView.tsx b/src/components/ReportDialog/SubmitView.tsx new file mode 100644 index 000000000..d47211c81 --- /dev/null +++ b/src/components/ReportDialog/SubmitView.tsx @@ -0,0 +1,264 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {AppBskyLabelerDefs} from '@atproto/api' + +import {getLabelingServiceTitle} from '#/lib/moderation' +import {ReportOption} from '#/lib/moderation/useReportOptions' + +import {atoms as a, useTheme, native} from '#/alf' +import {Text} from '#/components/Typography' +import * as Dialog from '#/components/Dialog' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' +import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' +import * as Toggle from '#/components/forms/Toggle' +import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' +import {Loader} from '#/components/Loader' +import * as Toast from '#/view/com/util/Toast' + +import {ReportDialogProps} from './types' +import {getAgent} from '#/state/session' + +export function SubmitView({ + params, + labelers, + selectedLabeler, + selectedReportOption, + goBack, + onSubmitComplete, +}: ReportDialogProps & { + labelers: AppBskyLabelerDefs.LabelerViewDetailed[] + selectedLabeler: string + selectedReportOption: ReportOption + goBack: () => void + onSubmitComplete: () => void +}) { + const t = useTheme() + const {_} = useLingui() + const [details, setDetails] = React.useState<string>('') + const [submitting, setSubmitting] = React.useState<boolean>(false) + const [selectedServices, setSelectedServices] = React.useState<string[]>([ + selectedLabeler, + ]) + const [error, setError] = React.useState('') + + const submit = React.useCallback(async () => { + setSubmitting(true) + setError('') + + const $type = + params.type === 'account' + ? 'com.atproto.admin.defs#repoRef' + : 'com.atproto.repo.strongRef' + const report = { + reasonType: selectedReportOption.reason, + subject: { + $type, + ...params, + }, + reason: details, + } + const results = await Promise.all( + selectedServices.map(did => + getAgent() + .withProxy('atproto_labeler', did) + .createModerationReport(report) + .then( + _ => true, + _ => false, + ), + ), + ) + + setSubmitting(false) + + if (results.includes(true)) { + Toast.show(_(msg`Thank you. Your report has been sent.`)) + onSubmitComplete() + } else { + setError( + _( + msg`There was an issue sending your report. Please check your internet connection.`, + ), + ) + } + }, [ + _, + params, + details, + selectedReportOption, + selectedServices, + onSubmitComplete, + setError, + ]) + + return ( + <View style={[a.gap_2xl]}> + <Button + size="small" + variant="solid" + color="secondary" + shape="round" + label={_(msg`Go back to previous step`)} + onPress={goBack}> + <ButtonIcon icon={ChevronLeft} /> + </Button> + + <View + style={[ + a.w_full, + a.flex_row, + a.align_center, + a.justify_between, + a.gap_lg, + a.p_md, + a.rounded_md, + a.border, + t.atoms.border_contrast_low, + ]}> + <View style={[a.flex_1, a.gap_xs]}> + <Text style={[a.text_md, a.font_bold]}> + {selectedReportOption.title} + </Text> + <Text style={[a.leading_tight, {maxWidth: 400}]}> + {selectedReportOption.description} + </Text> + </View> + + <Check size="md" style={[a.pr_sm, t.atoms.text_contrast_low]} /> + </View> + + <View style={[a.gap_md]}> + <Text style={[t.atoms.text_contrast_medium]}> + <Trans>Select the moderation service(s) to report to</Trans> + </Text> + + <Toggle.Group + label="Select mod services" + values={selectedServices} + onChange={setSelectedServices}> + <View style={[a.flex_row, a.gap_md, a.flex_wrap]}> + {labelers.map(labeler => { + const title = getLabelingServiceTitle({ + displayName: labeler.creator.displayName, + handle: labeler.creator.handle, + }) + return ( + <Toggle.Item + key={labeler.creator.did} + name={labeler.creator.did} + label={title}> + <LabelerToggle title={title} /> + </Toggle.Item> + ) + })} + </View> + </Toggle.Group> + </View> + <View style={[a.gap_md]}> + <Text style={[t.atoms.text_contrast_medium]}> + <Trans>Optionally provide additional information below:</Trans> + </Text> + + <View style={[a.relative, a.w_full]}> + <Dialog.Input + multiline + value={details} + onChangeText={setDetails} + label="Text field" + style={{paddingRight: 60}} + numberOfLines={6} + /> + + <View + style={[ + a.absolute, + a.flex_row, + a.align_center, + a.pr_md, + a.pb_sm, + { + bottom: 0, + right: 0, + }, + ]}> + <CharProgress count={details?.length || 0} /> + </View> + </View> + </View> + + <View style={[a.flex_row, a.align_center, a.justify_end, a.gap_lg]}> + {!selectedServices.length || + (error && ( + <Text + style={[ + a.flex_1, + a.italic, + a.leading_snug, + t.atoms.text_contrast_medium, + ]}> + {error ? ( + error + ) : ( + <Trans>You must select at least one labeler for a report</Trans> + )} + </Text> + ))} + + <Button + size="large" + variant="solid" + color="negative" + label={_(msg`Send report`)} + onPress={submit} + disabled={!selectedServices.length}> + <ButtonText> + <Trans>Send report</Trans> + </ButtonText> + {submitting && <ButtonIcon icon={Loader} />} + </Button> + </View> + </View> + ) +} + +function LabelerToggle({title}: {title: string}) { + const t = useTheme() + const ctx = Toggle.useItemContext() + + return ( + <View + style={[ + a.flex_row, + a.align_center, + a.gap_md, + a.p_md, + a.pr_lg, + a.rounded_sm, + a.overflow_hidden, + t.atoms.bg_contrast_25, + ctx.selected && [t.atoms.bg_contrast_50], + ]}> + <Toggle.Checkbox /> + <View + style={[ + a.flex_row, + a.align_center, + a.justify_between, + a.gap_lg, + a.z_10, + ]}> + <Text + style={[ + native({marginTop: 2}), + t.atoms.text_contrast_medium, + ctx.selected && t.atoms.text, + ]}> + {title} + </Text> + </View> + </View> + ) +} diff --git a/src/components/ReportDialog/const.ts b/src/components/ReportDialog/const.ts new file mode 100644 index 000000000..30c9aff88 --- /dev/null +++ b/src/components/ReportDialog/const.ts @@ -0,0 +1 @@ +export const DMCA_LINK = 'https://bsky.social/about/support/copyright' diff --git a/src/components/ReportDialog/index.tsx b/src/components/ReportDialog/index.tsx new file mode 100644 index 000000000..f01ff3f3b --- /dev/null +++ b/src/components/ReportDialog/index.tsx @@ -0,0 +1,95 @@ +import React from 'react' +import {View, Pressable} from 'react-native' +import {Trans} from '@lingui/macro' + +import {useMyLabelersQuery} from '#/state/queries/preferences' +import {ReportOption} from '#/lib/moderation/useReportOptions' +export {useDialogControl as useReportDialogControl} from '#/components/Dialog' + +import {atoms as a} from '#/alf' +import {Loader} from '#/components/Loader' +import * as Dialog from '#/components/Dialog' +import {Text} from '#/components/Typography' + +import {ReportDialogProps} from './types' +import {SelectLabelerView} from './SelectLabelerView' +import {SelectReportOptionView} from './SelectReportOptionView' +import {SubmitView} from './SubmitView' +import {useDelayedLoading} from '#/components/hooks/useDelayedLoading' +import {AppBskyLabelerDefs} from '@atproto/api' + +export function ReportDialog(props: ReportDialogProps) { + return ( + <Dialog.Outer control={props.control}> + <Dialog.Handle /> + + <ReportDialogInner {...props} /> + </Dialog.Outer> + ) +} + +function ReportDialogInner(props: ReportDialogProps) { + const { + isLoading: isLabelerLoading, + data: labelers, + error, + } = useMyLabelersQuery() + const isLoading = useDelayedLoading(500, isLabelerLoading) + + return ( + <Dialog.ScrollableInner label="Report Dialog"> + {isLoading ? ( + <View style={[a.align_center, {height: 100}]}> + <Loader size="xl" /> + {/* Here to capture focus for a hot sec to prevent flash */} + <Pressable accessible={false} /> + </View> + ) : error || !labelers ? ( + <View> + <Text style={[a.text_md]}> + <Trans>Something went wrong, please try again.</Trans> + </Text> + </View> + ) : ( + <ReportDialogLoaded labelers={labelers} {...props} /> + )} + + <Dialog.Close /> + </Dialog.ScrollableInner> + ) +} + +function ReportDialogLoaded( + props: ReportDialogProps & { + labelers: AppBskyLabelerDefs.LabelerViewDetailed[] + }, +) { + const [selectedLabeler, setSelectedLabeler] = React.useState< + string | undefined + >(props.labelers.length === 1 ? props.labelers[0].creator.did : undefined) + const [selectedReportOption, setSelectedReportOption] = React.useState< + ReportOption | undefined + >() + + if (selectedReportOption && selectedLabeler) { + return ( + <SubmitView + {...props} + selectedLabeler={selectedLabeler} + selectedReportOption={selectedReportOption} + goBack={() => setSelectedReportOption(undefined)} + onSubmitComplete={() => props.control.close()} + /> + ) + } + if (selectedLabeler) { + return ( + <SelectReportOptionView + {...props} + goBack={() => setSelectedLabeler(undefined)} + onSelectReportOption={setSelectedReportOption} + /> + ) + } + return <SelectLabelerView {...props} onSelectLabeler={setSelectedLabeler} /> +} diff --git a/src/components/ReportDialog/types.ts b/src/components/ReportDialog/types.ts new file mode 100644 index 000000000..0c8a1e077 --- /dev/null +++ b/src/components/ReportDialog/types.ts @@ -0,0 +1,15 @@ +import * as Dialog from '#/components/Dialog' + +export type ReportDialogProps = { + control: Dialog.DialogOuterProps['control'] + params: + | { + type: 'post' | 'list' | 'feedgen' | 'other' + uri: string + cid: string + } + | { + type: 'account' + did: string + } +} diff --git a/src/components/TagMenu/index.tsx b/src/components/TagMenu/index.tsx index 849a3f42d..0ed703667 100644 --- a/src/components/TagMenu/index.tsx +++ b/src/components/TagMenu/index.tsx @@ -59,7 +59,7 @@ export function TagMenu({ const displayTag = '#' + tag const isMuted = Boolean( - (preferences?.mutedWords?.find( + (preferences?.moderationPrefs.mutedWords?.find( m => m.value === tag && m.targets.includes('tag'), ) ?? optimisticUpsert?.find( @@ -264,7 +264,9 @@ export function TagMenu({ variant="ghost" color="secondary" onPress={() => control.close()}> - <ButtonText>Cancel</ButtonText> + <ButtonText> + <Trans>Cancel</Trans> + </ButtonText> </Button> </> )} diff --git a/src/components/TagMenu/index.web.tsx b/src/components/TagMenu/index.web.tsx index 8245bd019..b2f5c9075 100644 --- a/src/components/TagMenu/index.web.tsx +++ b/src/components/TagMenu/index.web.tsx @@ -50,7 +50,7 @@ export function TagMenu({ const {mutateAsync: removeMutedWord, variables: optimisticRemove} = useRemoveMutedWordMutation() const isMuted = Boolean( - (preferences?.mutedWords?.find( + (preferences?.moderationPrefs.mutedWords?.find( m => m.value === tag && m.targets.includes('tag'), ) ?? optimisticUpsert?.find( diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx index 5268e7f46..f8b3ad1bd 100644 --- a/src/components/Typography.tsx +++ b/src/components/Typography.tsx @@ -1,5 +1,10 @@ import React from 'react' -import {Text as RNText, TextStyle, TextProps as RNTextProps} from 'react-native' +import { + Text as RNText, + StyleProp, + TextStyle, + TextProps as RNTextProps, +} from 'react-native' import {UITextView} from 'react-native-ui-text-view' import {useTheme, atoms, web, flatten} from '#/alf' @@ -34,7 +39,7 @@ export function leading< * If the `lineHeight` value is > 2, we assume it's an absolute value and * returns it as-is. */ -function normalizeTextStyles(styles: TextStyle[]) { +export function normalizeTextStyles(styles: StyleProp<TextStyle>) { const s = flatten(styles) // should always be defined on these components const fontSize = s.fontSize || atoms.text_md.fontSize diff --git a/src/components/dialogs/BirthDateSettings.tsx b/src/components/dialogs/BirthDateSettings.tsx index 62c95c78d..4a3e96e56 100644 --- a/src/components/dialogs/BirthDateSettings.tsx +++ b/src/components/dialogs/BirthDateSettings.tsx @@ -1,48 +1,64 @@ import React from 'react' import {useLingui} from '@lingui/react' import {Trans, msg} from '@lingui/macro' +import {View} from 'react-native' import * as Dialog from '#/components/Dialog' import {Text} from '../Typography' import {DateInput} from '#/view/com/util/forms/DateInput' import {logger} from '#/logger' import { + usePreferencesQuery, usePreferencesSetBirthDateMutation, UsePreferencesQueryResponse, } from '#/state/queries/preferences' -import {Button, ButtonText} from '../Button' +import {Button, ButtonIcon, ButtonText} from '../Button' import {atoms as a, useTheme} from '#/alf' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' import {cleanError} from '#/lib/strings/errors' -import {ActivityIndicator, View} from 'react-native' import {isIOS, isWeb} from '#/platform/detection' +import {Loader} from '#/components/Loader' export function BirthDateSettingsDialog({ control, - preferences, }: { control: Dialog.DialogControlProps - preferences: UsePreferencesQueryResponse | undefined }) { + const t = useTheme() const {_} = useLingui() - const {isPending, isError, error, mutateAsync} = - usePreferencesSetBirthDateMutation() + const {isLoading, error, data: preferences} = usePreferencesQuery() return ( <Dialog.Outer control={control}> <Dialog.Handle /> + <Dialog.ScrollableInner label={_(msg`My Birthday`)}> - {preferences && !isPending ? ( - <BirthdayInner - control={control} - preferences={preferences} - isError={isError} - error={error} - setBirthDate={mutateAsync} + <View style={[a.gap_sm, a.pb_lg]}> + <Text style={[a.text_2xl, a.font_bold]}> + <Trans>My Birthday</Trans> + </Text> + <Text style={[a.text_md, t.atoms.text_contrast_medium]}> + <Trans>This information is not shared with other users.</Trans> + </Text> + </View> + + {isLoading ? ( + <Loader size="xl" /> + ) : error || !preferences ? ( + <ErrorMessage + message={ + error?.toString() || + _( + msg`We were unable to load your birth date preferences. Please try again.`, + ) + } + style={[a.rounded_sm]} /> ) : ( - <ActivityIndicator size="large" style={a.my_5xl} /> + <BirthdayInner control={control} preferences={preferences} /> )} + + <Dialog.Close /> </Dialog.ScrollableInner> </Dialog.Outer> ) @@ -51,20 +67,18 @@ export function BirthDateSettingsDialog({ function BirthdayInner({ control, preferences, - isError, - error, - setBirthDate, }: { control: Dialog.DialogControlProps preferences: UsePreferencesQueryResponse - isError: boolean - error: unknown - setBirthDate: (args: {birthDate: Date}) => Promise<unknown> }) { const {_} = useLingui() const [date, setDate] = React.useState(preferences.birthDate || new Date()) - const t = useTheme() - + const { + isPending, + isError, + error, + mutateAsync: setBirthDate, + } = usePreferencesSetBirthDateMutation() const hasChanged = date !== preferences.birthDate const onSave = React.useCallback(async () => { @@ -74,21 +88,13 @@ function BirthdayInner({ await setBirthDate({birthDate: date}) } control.close() - } catch (e) { - logger.error(`setBirthDate failed`, {message: e}) + } catch (e: any) { + logger.error(`setBirthDate failed`, {message: e.message}) } }, [date, setBirthDate, control, hasChanged]) return ( <View style={a.gap_lg} testID="birthDateSettingsDialog"> - <View style={[a.gap_sm]}> - <Text style={[a.text_2xl, a.font_bold]}> - <Trans>My Birthday</Trans> - </Text> - <Text style={t.atoms.text_contrast_medium}> - <Trans>This information is not shared with other users.</Trans> - </Text> - </View> <View style={isIOS && [a.w_full, a.align_center]}> <DateInput handleAsUTC @@ -103,6 +109,7 @@ function BirthdayInner({ accessibilityLabelledBy="birthDate" /> </View> + {isError ? ( <ErrorMessage message={cleanError(error)} style={[a.rounded_sm]} /> ) : undefined} @@ -110,13 +117,14 @@ function BirthdayInner({ <View style={isWeb && [a.flex_row, a.justify_end]}> <Button label={hasChanged ? _(msg`Save birthday`) : _(msg`Done`)} - size={isWeb ? 'small' : 'medium'} + size="medium" onPress={onSave} variant="solid" color="primary"> <ButtonText> {hasChanged ? <Trans>Save</Trans> : <Trans>Done</Trans>} </ButtonText> + {isPending && <ButtonIcon icon={Loader} />} </Button> </View> </View> diff --git a/src/components/dialogs/Context.tsx b/src/components/dialogs/Context.tsx index d86c90a92..87bd5c2ed 100644 --- a/src/components/dialogs/Context.tsx +++ b/src/components/dialogs/Context.tsx @@ -18,7 +18,7 @@ export function useGlobalDialogsControlContext() { export function Provider({children}: React.PropsWithChildren<{}>) { const mutedWordsDialogControl = Dialog.useDialogControl() - const ctx = React.useMemo( + const ctx = React.useMemo<ControlsContext>( () => ({mutedWordsDialogControl}), [mutedWordsDialogControl], ) diff --git a/src/components/dialogs/MutedWords.tsx b/src/components/dialogs/MutedWords.tsx index dcdaccea3..46f319adf 100644 --- a/src/components/dialogs/MutedWords.tsx +++ b/src/components/dialogs/MutedWords.tsx @@ -233,8 +233,8 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) { </Trans> </Text> </View> - ) : preferences.mutedWords.length ? ( - [...preferences.mutedWords] + ) : preferences.moderationPrefs.mutedWords.length ? ( + [...preferences.moderationPrefs.mutedWords] .reverse() .map((word, i) => ( <MutedWordRow diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx index a83f92a2a..7a4b5ac95 100644 --- a/src/components/forms/Toggle.tsx +++ b/src/components/forms/Toggle.tsx @@ -2,7 +2,14 @@ import React from 'react' import {Pressable, View, ViewStyle} from 'react-native' import {HITSLOP_10} from 'lib/constants' -import {useTheme, atoms as a, web, native, flatten, ViewStyleProp} from '#/alf' +import { + useTheme, + atoms as a, + native, + flatten, + ViewStyleProp, + TextStyleProp, +} from '#/alf' import {Text} from '#/components/Typography' import {useInteractionState} from '#/components/hooks/useInteractionState' import {CheckThick_Stroke2_Corner0_Rounded as Checkmark} from '#/components/icons/Check' @@ -220,20 +227,17 @@ export function Item({ onPressOut={onPressOut} onFocus={onFocus} onBlur={onBlur} - style={[ - a.flex_row, - a.align_center, - a.gap_sm, - focused ? web({outline: 'none'}) : {}, - flatten(style), - ]}> + style={[a.flex_row, a.align_center, a.gap_sm, flatten(style)]}> {typeof children === 'function' ? children(state) : children} </Pressable> </ItemContext.Provider> ) } -export function Label({children}: React.PropsWithChildren<{}>) { +export function Label({ + children, + style, +}: React.PropsWithChildren<TextStyleProp>) { const t = useTheme() const {disabled} = useItemContext() return ( @@ -242,11 +246,14 @@ export function Label({children}: React.PropsWithChildren<{}>) { a.font_bold, { userSelect: 'none', - color: disabled ? t.palette.contrast_400 : t.palette.contrast_600, + color: disabled + ? t.atoms.text_contrast_low.color + : t.atoms.text_contrast_high.color, }, native({ paddingTop: 3, }), + flatten(style), ]}> {children} </Text> @@ -257,7 +264,6 @@ export function Label({children}: React.PropsWithChildren<{}>) { export function createSharedToggleStyles({ theme: t, hovered, - focused, selected, disabled, isInvalid, @@ -280,7 +286,7 @@ export function createSharedToggleStyles({ borderColor: t.palette.primary_500, }) - if (hovered || focused) { + if (hovered) { baseHover.push({ backgroundColor: t.name === 'light' ? t.palette.primary_100 : t.palette.primary_800, @@ -289,7 +295,7 @@ export function createSharedToggleStyles({ }) } } else { - if (hovered || focused) { + if (hovered) { baseHover.push({ backgroundColor: t.name === 'light' ? t.palette.contrast_50 : t.palette.contrast_100, @@ -306,7 +312,7 @@ export function createSharedToggleStyles({ t.name === 'light' ? t.palette.negative_300 : t.palette.negative_800, }) - if (hovered || focused) { + if (hovered) { baseHover.push({ backgroundColor: t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900, @@ -353,7 +359,7 @@ export function Checkbox() { width: 20, }, baseStyles, - hovered || focused ? baseHoverStyles : {}, + hovered ? baseHoverStyles : {}, ]}> {selected ? <Checkmark size="xs" fill={t.palette.primary_500} /> : null} </View> @@ -385,7 +391,7 @@ export function Switch() { width: 30, }, baseStyles, - hovered || focused ? baseHoverStyles : {}, + hovered ? baseHoverStyles : {}, ]}> <View style={[ @@ -437,7 +443,7 @@ export function Radio() { width: 20, }, baseStyles, - hovered || focused ? baseHoverStyles : {}, + hovered ? baseHoverStyles : {}, ]}> {selected ? ( <View diff --git a/src/components/forms/ToggleButton.tsx b/src/components/forms/ToggleButton.tsx index 7e1bd70b9..9cdaaaa9d 100644 --- a/src/components/forms/ToggleButton.tsx +++ b/src/components/forms/ToggleButton.tsx @@ -8,7 +8,9 @@ import * as Toggle from '#/components/forms/Toggle' export type ItemProps = Omit<Toggle.ItemProps, 'style' | 'role' | 'children'> & AccessibilityProps & - React.PropsWithChildren<{testID?: string}> + React.PropsWithChildren<{ + testID?: string + }> export type GroupProps = Omit<Toggle.GroupProps, 'style' | 'type'> & { multiple?: boolean @@ -101,12 +103,12 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) { native({ paddingBottom: 10, }), - a.px_sm, + a.px_md, t.atoms.bg, t.atoms.border_contrast_low, baseStyles, activeStyles, - (state.hovered || state.focused || state.pressed) && hoverStyles, + (state.hovered || state.pressed) && hoverStyles, ]}> {typeof children === 'string' ? ( <Text diff --git a/src/components/hooks/useDelayedLoading.ts b/src/components/hooks/useDelayedLoading.ts new file mode 100644 index 000000000..6c7e2ede0 --- /dev/null +++ b/src/components/hooks/useDelayedLoading.ts @@ -0,0 +1,15 @@ +import React from 'react' + +export function useDelayedLoading(delay: number, initialState: boolean = true) { + const [isLoading, setIsLoading] = React.useState(initialState) + + React.useEffect(() => { + let timeout: NodeJS.Timeout + // on initial load, show a loading spinner for a hot sec to prevent flash + if (isLoading) timeout = setTimeout(() => setIsLoading(false), delay) + + return () => timeout && clearTimeout(timeout) + }, [isLoading, delay]) + + return isLoading +} diff --git a/src/components/icons/ArrowTriangle.tsx b/src/components/icons/ArrowTriangle.tsx new file mode 100644 index 000000000..b27b719ae --- /dev/null +++ b/src/components/icons/ArrowTriangle.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const ArrowTriangleBottom_Stroke2_Corner1_Rounded = createSinglePathSVG({ + path: 'M4.213 6.886c-.673-1.35.334-2.889 1.806-2.889H17.98c1.472 0 2.479 1.539 1.806 2.89l-5.982 11.997c-.74 1.484-2.87 1.484-3.61 0L4.213 6.886Z', +}) diff --git a/src/components/icons/Bars.tsx b/src/components/icons/Bars.tsx new file mode 100644 index 000000000..7b1415a4b --- /dev/null +++ b/src/components/icons/Bars.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Bars3_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M3 5a1 1 0 0 0 0 2h18a1 1 0 1 0 0-2H3Zm-1 7a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm0 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Z', +}) diff --git a/src/components/icons/Chevron.tsx b/src/components/icons/Chevron.tsx index b1a9deea0..a04e6e009 100644 --- a/src/components/icons/Chevron.tsx +++ b/src/components/icons/Chevron.tsx @@ -7,3 +7,11 @@ export const ChevronLeft_Stroke2_Corner0_Rounded = createSinglePathSVG({ export const ChevronRight_Stroke2_Corner0_Rounded = createSinglePathSVG({ path: 'M8.293 3.293a1 1 0 0 1 1.414 0l8 8a1 1 0 0 1 0 1.414l-8 8a1 1 0 0 1-1.414-1.414L15.586 12 8.293 4.707a1 1 0 0 1 0-1.414Z', }) + +export const ChevronTop_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M12 6a1 1 0 0 1 .707.293l8 8a1 1 0 0 1-1.414 1.414L12 8.414l-7.293 7.293a1 1 0 0 1-1.414-1.414l8-8A1 1 0 0 1 12 6Z', +}) + +export const ChevronBottom_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M3.293 8.293a1 1 0 0 1 1.414 0L12 15.586l7.293-7.293a1 1 0 1 1 1.414 1.414l-8 8a1 1 0 0 1-1.414 0l-8-8a1 1 0 0 1 0-1.414Z', +}) diff --git a/src/components/icons/CircleBanSign.tsx b/src/components/icons/CircleBanSign.tsx new file mode 100644 index 000000000..543985d43 --- /dev/null +++ b/src/components/icons/CircleBanSign.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const CircleBanSign_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M12 4a8 8 0 0 0-6.32 12.906L16.906 5.68A7.962 7.962 0 0 0 12 4Zm6.32 3.094L7.094 18.32A8 8 0 0 0 18.32 7.094ZM2 12C2 6.477 6.477 2 12 2a9.972 9.972 0 0 1 7.071 2.929A9.972 9.972 0 0 1 22 12c0 5.523-4.477 10-10 10a9.972 9.972 0 0 1-7.071-2.929A9.972 9.972 0 0 1 2 12Z', +}) diff --git a/src/components/icons/Gear.tsx b/src/components/icons/Gear.tsx new file mode 100644 index 000000000..980b7413b --- /dev/null +++ b/src/components/icons/Gear.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const SettingsGear2_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M11.1 2a1 1 0 0 0-.832.445L8.851 4.57 6.6 4.05a1 1 0 0 0-.932.268l-1.35 1.35a1 1 0 0 0-.267.932l.52 2.251-2.126 1.417A1 1 0 0 0 2 11.1v1.8a1 1 0 0 0 .445.832l2.125 1.417-.52 2.251a1 1 0 0 0 .268.932l1.35 1.35a1 1 0 0 0 .932.267l2.251-.52 1.417 2.126A1 1 0 0 0 11.1 22h1.8a1 1 0 0 0 .832-.445l1.417-2.125 2.251.52a1 1 0 0 0 .932-.268l1.35-1.35a1 1 0 0 0 .267-.932l-.52-2.251 2.126-1.417A1 1 0 0 0 22 12.9v-1.8a1 1 0 0 0-.445-.832L19.43 8.851l.52-2.251a1 1 0 0 0-.268-.932l-1.35-1.35a1 1 0 0 0-.932-.267l-2.251.52-1.417-2.126A1 1 0 0 0 12.9 2h-1.8Zm-.968 4.255L11.635 4h.73l1.503 2.255a1 1 0 0 0 1.057.42l2.385-.551.566.566-.55 2.385a1 1 0 0 0 .42 1.057L20 11.635v.73l-2.255 1.503a1 1 0 0 0-.42 1.057l.551 2.385-.566.566-2.385-.55a1 1 0 0 0-1.057.42L12.365 20h-.73l-1.503-2.255a1 1 0 0 0-1.057-.42l-2.385.551-.566-.566.55-2.385a1 1 0 0 0-.42-1.057L4 12.365v-.73l2.255-1.503a1 1 0 0 0 .42-1.057L6.123 6.69l.566-.566 2.385.55a1 1 0 0 0 1.057-.42ZM8 12a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm4-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z', +}) diff --git a/src/components/icons/Group3.tsx b/src/components/icons/Group.tsx index 9e5ab8893..9e5ab8893 100644 --- a/src/components/icons/Group3.tsx +++ b/src/components/icons/Group.tsx diff --git a/src/components/icons/RaisingHand.tsx b/src/components/icons/RaisingHand.tsx new file mode 100644 index 000000000..cd023cb7e --- /dev/null +++ b/src/components/icons/RaisingHand.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const RaisingHande4Finger_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M10.25 4a.75.75 0 0 0-.75.75V11a1 1 0 1 1-2 0V6.75a.75.75 0 0 0-1.5 0V14a6 6 0 0 0 12 0V9a2 2 0 0 0-2 2v1.5a1 1 0 0 1-.684.949l-.628.21A2.469 2.469 0 0 0 13 16a1 1 0 1 1-2 0 4.469 4.469 0 0 1 3-4.22V11c0-.703.181-1.364.5-1.938V5.75a.75.75 0 0 0-1.5 0V9a1 1 0 1 1-2 0V4.75a.75.75 0 0 0-.75-.75Zm2.316-.733A2.75 2.75 0 0 1 16.5 5.75v1.54c.463-.187.97-.29 1.5-.29h1a1 1 0 0 1 1 1v6a8 8 0 1 1-16 0V6.75a2.75 2.75 0 0 1 3.571-2.625 2.751 2.751 0 0 1 4.995-.858Z', +}) diff --git a/src/components/icons/Shield.tsx b/src/components/icons/Shield.tsx new file mode 100644 index 000000000..5038d5c24 --- /dev/null +++ b/src/components/icons/Shield.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Shield_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M11.675 2.054a1 1 0 0 1 .65 0l8 2.75A1 1 0 0 1 21 5.75v6.162c0 2.807-1.149 4.83-2.813 6.405-1.572 1.488-3.632 2.6-5.555 3.636l-.157.085a1 1 0 0 1-.95 0l-.157-.085c-1.923-1.037-3.983-2.148-5.556-3.636C4.15 16.742 3 14.719 3 11.912V5.75a1 1 0 0 1 .675-.946l8-2.75ZM5 6.464v5.448c0 2.166.851 3.687 2.188 4.952 1.276 1.209 2.964 2.158 4.812 3.157 1.848-1 3.536-1.948 4.813-3.157C18.148 15.6 19 14.078 19 11.912V6.464l-7-2.407-7 2.407Z', +}) diff --git a/src/components/icons/SquareArrowTopRight.tsx b/src/components/icons/SquareArrowTopRight.tsx new file mode 100644 index 000000000..7701e26e5 --- /dev/null +++ b/src/components/icons/SquareArrowTopRight.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const SquareArrowTopRight_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M14 5a1 1 0 1 1 0-2h6a1 1 0 0 1 1 1v6a1 1 0 1 1-2 0V6.414l-7.293 7.293a1 1 0 0 1-1.414-1.414L17.586 5H14ZM3 6a1 1 0 0 1 1-1h5a1 1 0 0 1 0 2H5v12h12v-4a1 1 0 1 1 2 0v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6Z', +}) diff --git a/src/components/icons/SquareBehindSquare4.tsx b/src/components/icons/SquareBehindSquare4.tsx new file mode 100644 index 000000000..425599cbc --- /dev/null +++ b/src/components/icons/SquareBehindSquare4.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const SquareBehindSquare4_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M8 8V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1h-5v5a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h5Zm1 8a1 1 0 0 1-1-1v-5H4v10h10v-4H9Z', +}) diff --git a/src/components/moderation/ContentHider.tsx b/src/components/moderation/ContentHider.tsx new file mode 100644 index 000000000..1e8f36d31 --- /dev/null +++ b/src/components/moderation/ContentHider.tsx @@ -0,0 +1,182 @@ +import React from 'react' +import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' +import {ModerationUI} from '@atproto/api' +import {useLingui} from '@lingui/react' +import {msg, Trans} from '@lingui/macro' + +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {isJustAMute} from '#/lib/moderation' +import {sanitizeDisplayName} from '#/lib/strings/display-names' + +import {atoms as a, useTheme, useBreakpoints, web} from '#/alf' +import {Button} from '#/components/Button' +import {Text} from '#/components/Typography' +import { + ModerationDetailsDialog, + useModerationDetailsDialogControl, +} from '#/components/moderation/ModerationDetailsDialog' + +export function ContentHider({ + testID, + modui, + ignoreMute, + style, + childContainerStyle, + children, +}: React.PropsWithChildren<{ + testID?: string + modui: ModerationUI | undefined + ignoreMute?: boolean + style?: StyleProp<ViewStyle> + childContainerStyle?: StyleProp<ViewStyle> +}>) { + const t = useTheme() + const {_} = useLingui() + const {gtMobile} = useBreakpoints() + const [override, setOverride] = React.useState(false) + const control = useModerationDetailsDialogControl() + + const blur = modui?.blurs[0] + const desc = useModerationCauseDescription(blur) + + if (!blur || (ignoreMute && isJustAMute(modui))) { + return ( + <View testID={testID} style={[styles.outer, style]}> + {children} + </View> + ) + } + + return ( + <View testID={testID} style={[a.overflow_hidden, style]}> + <ModerationDetailsDialog control={control} modcause={blur} /> + + <Button + onPress={() => { + if (!modui.noOverride) { + setOverride(v => !v) + } else { + control.open() + } + }} + label={desc.name} + accessibilityHint={ + modui.noOverride + ? _(msg`Learn more about the moderation applied to this content.`) + : override + ? _(msg`Hide the content`) + : _(msg`Show the content`) + }> + {state => ( + <View + style={[ + a.flex_row, + a.w_full, + a.justify_start, + a.align_center, + a.py_md, + a.px_lg, + a.gap_xs, + a.rounded_sm, + t.atoms.bg_contrast_25, + gtMobile && [a.gap_sm, a.py_lg, a.mt_xs, a.px_xl], + (state.hovered || state.pressed) && t.atoms.bg_contrast_50, + ]}> + <desc.icon + size="md" + fill={t.atoms.text_contrast_medium.color} + style={{marginLeft: -2}} + /> + <Text + style={[ + a.flex_1, + a.text_left, + a.font_bold, + a.leading_snug, + gtMobile && [a.font_semibold], + t.atoms.text_contrast_medium, + web({ + marginBottom: 1, + }), + ]}> + {desc.name} + </Text> + {!modui.noOverride && ( + <Text + style={[ + a.font_bold, + a.leading_snug, + gtMobile && [a.font_semibold], + t.atoms.text_contrast_high, + web({ + marginBottom: 1, + }), + ]}> + {override ? <Trans>Hide</Trans> : <Trans>Show</Trans>} + </Text> + )} + </View> + )} + </Button> + + {desc.source && blur.type === 'label' && !override && ( + <Button + onPress={() => { + control.open() + }} + label={_( + msg`Learn more about the moderation applied to this content.`, + )} + style={[a.pt_sm]}> + {state => ( + <Text + style={[ + a.flex_1, + a.text_sm, + a.font_normal, + a.leading_snug, + t.atoms.text_contrast_medium, + a.text_left, + ]}> + {desc.sourceType === 'user' ? ( + <Trans>Labeled by the author.</Trans> + ) : ( + <Trans>Labeled by {sanitizeDisplayName(desc.source!)}.</Trans> + )}{' '} + <Text + style={[ + {color: t.palette.primary_500}, + a.text_sm, + state.hovered && [web({textDecoration: 'underline'})], + ]}> + <Trans>Learn more.</Trans> + </Text> + </Text> + )} + </Button> + )} + + {override && <View style={childContainerStyle}>{children}</View>} + </View> + ) +} + +const styles = StyleSheet.create({ + outer: { + overflow: 'hidden', + }, + cover: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + borderRadius: 8, + marginTop: 4, + paddingVertical: 14, + paddingLeft: 14, + paddingRight: 18, + }, + showBtn: { + marginLeft: 'auto', + alignSelf: 'center', + }, +}) diff --git a/src/components/moderation/GlobalModerationLabelPref.tsx b/src/components/moderation/GlobalModerationLabelPref.tsx new file mode 100644 index 000000000..7633cb9f2 --- /dev/null +++ b/src/components/moderation/GlobalModerationLabelPref.tsx @@ -0,0 +1,93 @@ +import React from 'react' +import {View} from 'react-native' +import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api' +import {useLingui} from '@lingui/react' +import {msg} from '@lingui/macro' + +import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' +import { + usePreferencesQuery, + usePreferencesSetContentLabelMutation, +} from '#/state/queries/preferences' + +import {useTheme, atoms as a} from '#/alf' +import {Text} from '#/components/Typography' +import * as ToggleButton from '#/components/forms/ToggleButton' + +export function GlobalModerationLabelPref({ + labelValueDefinition, + disabled, +}: { + labelValueDefinition: InterpretedLabelValueDefinition + disabled?: boolean +}) { + const {_} = useLingui() + const t = useTheme() + + const {identifier} = labelValueDefinition + const {data: preferences} = usePreferencesQuery() + const {mutate, variables} = usePreferencesSetContentLabelMutation() + const savedPref = preferences?.moderationPrefs.labels[identifier] + const pref = variables?.visibility ?? savedPref ?? 'warn' + + const allLabelStrings = useGlobalLabelStrings() + const labelStrings = + labelValueDefinition.identifier in allLabelStrings + ? allLabelStrings[labelValueDefinition.identifier] + : { + name: labelValueDefinition.identifier, + description: `Labeled "${labelValueDefinition.identifier}"`, + } + + const labelOptions = { + hide: _(msg`Hide`), + warn: _(msg`Warn`), + ignore: _(msg`Show`), + } + + return ( + <View + style={[ + a.flex_row, + a.justify_between, + a.gap_sm, + a.py_md, + a.pl_lg, + a.pr_md, + a.align_center, + ]}> + <View style={[a.gap_xs, a.flex_1]}> + <Text style={[a.font_bold]}>{labelStrings.name}</Text> + <Text style={[t.atoms.text_contrast_medium, a.leading_snug]}> + {labelStrings.description} + </Text> + </View> + <View style={[a.justify_center, {minHeight: 35}]}> + {!disabled && ( + <ToggleButton.Group + label={_( + msg`Configure content filtering setting for category: ${labelStrings.name.toLowerCase()}`, + )} + values={[pref]} + onChange={newPref => + mutate({ + label: identifier, + visibility: newPref[0] as LabelPreference, + labelerDid: undefined, + }) + }> + <ToggleButton.Button name="ignore" label={labelOptions.ignore}> + {labelOptions.ignore} + </ToggleButton.Button> + <ToggleButton.Button name="warn" label={labelOptions.warn}> + {labelOptions.warn} + </ToggleButton.Button> + <ToggleButton.Button name="hide" label={labelOptions.hide}> + {labelOptions.hide} + </ToggleButton.Button> + </ToggleButton.Group> + )} + </View> + </View> + ) +} diff --git a/src/components/moderation/LabelsOnMe.tsx b/src/components/moderation/LabelsOnMe.tsx new file mode 100644 index 000000000..099769fa7 --- /dev/null +++ b/src/components/moderation/LabelsOnMe.tsx @@ -0,0 +1,83 @@ +import React from 'react' +import {StyleProp, View, ViewStyle} from 'react-native' +import {AppBskyFeedDefs, ComAtprotoLabelDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useSession} from '#/state/session' + +import {atoms as a} from '#/alf' +import {Button, ButtonText, ButtonIcon, ButtonSize} from '#/components/Button' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import { + LabelsOnMeDialog, + useLabelsOnMeDialogControl, +} from '#/components/moderation/LabelsOnMeDialog' + +export function LabelsOnMe({ + details, + labels, + size, + style, +}: { + details: {did: string} | {uri: string; cid: string} + labels: ComAtprotoLabelDefs.Label[] | undefined + size?: ButtonSize + style?: StyleProp<ViewStyle> +}) { + const {_} = useLingui() + const {currentAccount} = useSession() + const isAccount = 'did' in details + const control = useLabelsOnMeDialogControl() + + if (!labels || !currentAccount) { + return null + } + labels = labels.filter( + l => !l.val.startsWith('!') && l.src !== currentAccount.did, + ) + if (!labels.length) { + return null + } + + const labelTarget = isAccount ? _(msg`account`) : _(msg`content`) + return ( + <View style={[a.flex_row, style]}> + <LabelsOnMeDialog control={control} subject={details} labels={labels} /> + + <Button + variant="solid" + color="secondary" + size={size || 'small'} + label={_(msg`View information about these labels`)} + onPress={() => { + control.open() + }}> + <ButtonIcon position="left" icon={CircleInfo} /> + <ButtonText style={[a.leading_snug]}> + {labels.length}{' '} + {labels.length === 1 ? ( + <Trans>label has been placed on this {labelTarget}</Trans> + ) : ( + <Trans>labels have been placed on this {labelTarget}</Trans> + )} + </ButtonText> + </Button> + </View> + ) +} + +export function LabelsOnMyPost({ + post, + style, +}: { + post: AppBskyFeedDefs.PostView + style?: StyleProp<ViewStyle> +}) { + const {currentAccount} = useSession() + if (post.author.did !== currentAccount?.did) { + return null + } + return ( + <LabelsOnMe details={post} labels={post.labels} size="tiny" style={style} /> + ) +} diff --git a/src/components/moderation/LabelsOnMeDialog.tsx b/src/components/moderation/LabelsOnMeDialog.tsx new file mode 100644 index 000000000..6eddbc7ce --- /dev/null +++ b/src/components/moderation/LabelsOnMeDialog.tsx @@ -0,0 +1,262 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {ComAtprotoLabelDefs, ComAtprotoModerationDefs} from '@atproto/api' + +import {useLabelInfo} from '#/lib/moderation/useLabelInfo' +import {makeProfileLink} from '#/lib/routes/links' +import {sanitizeHandle} from '#/lib/strings/handles' +import {getAgent} from '#/state/session' + +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {Text} from '#/components/Typography' +import * as Dialog from '#/components/Dialog' +import {Button, ButtonText} from '#/components/Button' +import {InlineLink} from '#/components/Link' +import * as Toast from '#/view/com/util/Toast' +import {Divider} from '../Divider' + +export {useDialogControl as useLabelsOnMeDialogControl} from '#/components/Dialog' + +type Subject = + | { + uri: string + cid: string + } + | { + did: string + } + +export interface LabelsOnMeDialogProps { + control: Dialog.DialogOuterProps['control'] + subject: Subject + labels: ComAtprotoLabelDefs.Label[] +} + +export function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) { + const {_} = useLingui() + const [appealingLabel, setAppealingLabel] = React.useState< + ComAtprotoLabelDefs.Label | undefined + >(undefined) + const {subject, labels} = props + const isAccount = 'did' in subject + + return ( + <Dialog.ScrollableInner + label={ + isAccount + ? _(msg`The following labels were applied to your account.`) + : _(msg`The following labels were applied to your content.`) + }> + {appealingLabel ? ( + <AppealForm + label={appealingLabel} + subject={subject} + control={props.control} + onPressBack={() => setAppealingLabel(undefined)} + /> + ) : ( + <> + <Text style={[a.text_2xl, a.font_bold, a.pb_xs, a.leading_tight]}> + {isAccount ? ( + <Trans>Labels on your account</Trans> + ) : ( + <Trans>Labels on your content</Trans> + )} + </Text> + <Text style={[a.text_md, a.leading_snug]}> + <Trans> + You may appeal these labels if you feel they were placed in error. + </Trans> + </Text> + + <View style={[a.py_lg, a.gap_md]}> + {labels.map(label => ( + <Label + key={`${label.val}-${label.src}`} + label={label} + control={props.control} + onPressAppeal={label => setAppealingLabel(label)} + /> + ))} + </View> + </> + )} + + <Dialog.Close /> + </Dialog.ScrollableInner> + ) +} + +export function LabelsOnMeDialog(props: LabelsOnMeDialogProps) { + return ( + <Dialog.Outer control={props.control}> + <Dialog.Handle /> + + <LabelsOnMeDialogInner {...props} /> + </Dialog.Outer> + ) +} + +function Label({ + label, + control, + onPressAppeal, +}: { + label: ComAtprotoLabelDefs.Label + control: Dialog.DialogOuterProps['control'] + onPressAppeal: (label: ComAtprotoLabelDefs.Label) => void +}) { + const t = useTheme() + const {_} = useLingui() + const {labeler, strings} = useLabelInfo(label) + return ( + <View + style={[ + a.border, + t.atoms.border_contrast_low, + a.rounded_sm, + a.overflow_hidden, + ]}> + <View style={[a.p_md, a.gap_sm, a.flex_row]}> + <View style={[a.flex_1, a.gap_xs]}> + <Text style={[a.font_bold, a.text_md]}>{strings.name}</Text> + <Text style={[t.atoms.text_contrast_medium, a.leading_snug]}> + {strings.description} + </Text> + </View> + <View> + <Button + variant="solid" + color="secondary" + size="small" + label={_(msg`Appeal`)} + onPress={() => onPressAppeal(label)}> + <ButtonText> + <Trans>Appeal</Trans> + </ButtonText> + </Button> + </View> + </View> + + <Divider /> + + <View style={[a.px_md, a.py_sm, t.atoms.bg_contrast_25]}> + <Text style={[t.atoms.text_contrast_medium]}> + <Trans>Source:</Trans>{' '} + <InlineLink + to={makeProfileLink( + labeler ? labeler.creator : {did: label.src, handle: ''}, + )} + onPress={() => control.close()}> + {labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src} + </InlineLink> + </Text> + </View> + </View> + ) +} + +function AppealForm({ + label, + subject, + control, + onPressBack, +}: { + label: ComAtprotoLabelDefs.Label + subject: Subject + control: Dialog.DialogOuterProps['control'] + onPressBack: () => void +}) { + const {_} = useLingui() + const {labeler, strings} = useLabelInfo(label) + const {gtMobile} = useBreakpoints() + const [details, setDetails] = React.useState('') + const isAccountReport = 'did' in subject + + const onSubmit = async () => { + try { + const $type = !isAccountReport + ? 'com.atproto.repo.strongRef' + : 'com.atproto.admin.defs#repoRef' + await getAgent() + .withProxy('atproto_labeler', label.src) + .createModerationReport({ + reasonType: ComAtprotoModerationDefs.REASONAPPEAL, + subject: { + $type, + ...subject, + }, + reason: details, + }) + Toast.show(_(msg`Appeal submitted.`)) + } finally { + control.close() + } + } + + return ( + <> + <Text style={[a.text_2xl, a.font_bold, a.pb_xs, a.leading_tight]}> + <Trans>Appeal "{strings.name}" label</Trans> + </Text> + <Text style={[a.text_md, a.leading_snug]}> + <Trans> + This appeal will be sent to{' '} + <InlineLink + to={makeProfileLink( + labeler ? labeler.creator : {did: label.src, handle: ''}, + )} + onPress={() => control.close()} + style={[a.text_md, a.leading_snug]}> + {labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src} + </InlineLink> + . + </Trans> + </Text> + <View style={[a.my_md]}> + <Dialog.Input + label={_(msg`Text input field`)} + placeholder={_( + msg`Please explain why you think this label was incorrectly applied by ${ + labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src + }`, + )} + value={details} + onChangeText={setDetails} + autoFocus={true} + numberOfLines={3} + multiline + maxLength={300} + /> + </View> + + <View + style={ + gtMobile + ? [a.flex_row, a.justify_between] + : [{flexDirection: 'column-reverse'}, a.gap_sm] + }> + <Button + testID="backBtn" + variant="solid" + color="secondary" + size="medium" + onPress={onPressBack} + label={_(msg`Back`)}> + {_(msg`Back`)} + </Button> + <Button + testID="submitBtn" + variant="solid" + color="primary" + size="medium" + onPress={onSubmit} + label={_(msg`Submit`)}> + {_(msg`Submit`)} + </Button> + </View> + </> + ) +} diff --git a/src/components/moderation/ModerationDetailsDialog.tsx b/src/components/moderation/ModerationDetailsDialog.tsx new file mode 100644 index 000000000..da490cb43 --- /dev/null +++ b/src/components/moderation/ModerationDetailsDialog.tsx @@ -0,0 +1,148 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {ModerationCause} from '@atproto/api' + +import {listUriToHref} from '#/lib/strings/url-helpers' +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {makeProfileLink} from '#/lib/routes/links' + +import {isNative} from '#/platform/detection' +import {useTheme, atoms as a} from '#/alf' +import {Text} from '#/components/Typography' +import * as Dialog from '#/components/Dialog' +import {InlineLink} from '#/components/Link' +import {Divider} from '#/components/Divider' + +export {useDialogControl as useModerationDetailsDialogControl} from '#/components/Dialog' + +export interface ModerationDetailsDialogProps { + control: Dialog.DialogOuterProps['control'] + modcause: ModerationCause +} + +export function ModerationDetailsDialog(props: ModerationDetailsDialogProps) { + return ( + <Dialog.Outer control={props.control}> + <Dialog.Handle /> + <ModerationDetailsDialogInner {...props} /> + </Dialog.Outer> + ) +} + +function ModerationDetailsDialogInner({ + modcause, + control, +}: ModerationDetailsDialogProps & { + control: Dialog.DialogOuterProps['control'] +}) { + const t = useTheme() + const {_} = useLingui() + const desc = useModerationCauseDescription(modcause) + + let name + let description + if (!modcause) { + name = _(msg`Content Warning`) + description = _( + msg`Moderator has chosen to set a general warning on the content.`, + ) + } else if (modcause.type === 'blocking') { + if (modcause.source.type === 'list') { + const list = modcause.source.list + name = _(msg`User Blocked by List`) + description = ( + <Trans> + This user is included in the{' '} + <InlineLink to={listUriToHref(list.uri)} style={[a.text_sm]}> + {list.name} + </InlineLink>{' '} + list which you have blocked. + </Trans> + ) + } else { + name = _(msg`User Blocked`) + description = _( + msg`You have blocked this user. You cannot view their content.`, + ) + } + } else if (modcause.type === 'blocked-by') { + name = _(msg`User Blocks You`) + description = _( + msg`This user has blocked you. You cannot view their content.`, + ) + } else if (modcause.type === 'block-other') { + name = _(msg`Content Not Available`) + description = _( + msg`This content is not available because one of the users involved has blocked the other.`, + ) + } else if (modcause.type === 'muted') { + if (modcause.source.type === 'list') { + const list = modcause.source.list + name = _(msg`Account Muted by List`) + description = ( + <Trans> + This user is included in the{' '} + <InlineLink to={listUriToHref(list.uri)} style={[a.text_sm]}> + {list.name} + </InlineLink>{' '} + list which you have muted. + </Trans> + ) + } else { + name = _(msg`Account Muted`) + description = _(msg`You have muted this account.`) + } + } else if (modcause.type === 'mute-word') { + name = _(msg`Post Hidden by Muted Word`) + description = _(msg`You've chosen to hide a word or tag within this post.`) + } else if (modcause.type === 'hidden') { + name = _(msg`Post Hidden by You`) + description = _(msg`You have hidden this post.`) + } else if (modcause.type === 'label') { + name = desc.name + description = desc.description + } else { + // should never happen + name = '' + description = '' + } + + return ( + <Dialog.ScrollableInner label={_(msg`Moderation details`)}> + <Text style={[t.atoms.text, a.text_2xl, a.font_bold, a.mb_sm]}> + {name} + </Text> + <Text style={[t.atoms.text, a.text_md, a.mb_lg, a.leading_snug]}> + {description} + </Text> + + {modcause.type === 'label' && ( + <> + <Divider /> + <Text style={[t.atoms.text, a.text_md, a.leading_snug, a.mt_lg]}> + <Trans> + This label was applied by{' '} + {modcause.source.type === 'user' ? ( + <Trans>the author</Trans> + ) : ( + <InlineLink + to={makeProfileLink({did: modcause.label.src, handle: ''})} + onPress={() => control.close()} + style={a.text_md}> + {desc.source} + </InlineLink> + )} + . + </Trans> + </Text> + </> + )} + + {isNative && <View style={{height: 40}} />} + + <Dialog.Close /> + </Dialog.ScrollableInner> + ) +} diff --git a/src/components/moderation/ModerationLabelPref.tsx b/src/components/moderation/ModerationLabelPref.tsx new file mode 100644 index 000000000..b16c24859 --- /dev/null +++ b/src/components/moderation/ModerationLabelPref.tsx @@ -0,0 +1,177 @@ +import React from 'react' +import {View} from 'react-native' +import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api' +import {useLingui} from '@lingui/react' +import {msg, Trans} from '@lingui/macro' + +import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' +import {useLabelBehaviorDescription} from '#/lib/moderation/useLabelBehaviorDescription' +import { + usePreferencesQuery, + usePreferencesSetContentLabelMutation, +} from '#/state/queries/preferences' +import {getLabelStrings} from '#/lib/moderation/useLabelInfo' + +import {useTheme, atoms as a, useBreakpoints} from '#/alf' +import {Text} from '#/components/Typography' +import {InlineLink} from '#/components/Link' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '../icons/CircleInfo' +import * as ToggleButton from '#/components/forms/ToggleButton' + +export function ModerationLabelPref({ + labelValueDefinition, + labelerDid, + disabled, +}: { + labelValueDefinition: InterpretedLabelValueDefinition + labelerDid: string | undefined + disabled?: boolean +}) { + const {_, i18n} = useLingui() + const t = useTheme() + const {gtPhone} = useBreakpoints() + + const isGlobalLabel = !labelValueDefinition.definedBy + const {identifier} = labelValueDefinition + const {data: preferences} = usePreferencesQuery() + const {mutate, variables} = usePreferencesSetContentLabelMutation() + const savedPref = + labelerDid && !isGlobalLabel + ? preferences?.moderationPrefs.labelers.find(l => l.did === labelerDid) + ?.labels[identifier] + : preferences?.moderationPrefs.labels[identifier] + const pref = + variables?.visibility ?? + savedPref ?? + labelValueDefinition.defaultSetting ?? + 'warn' + + // does the 'warn' setting make sense for this label? + const canWarn = !( + labelValueDefinition.blurs === 'none' && + labelValueDefinition.severity === 'none' + ) + // is this label adult only? + const adultOnly = labelValueDefinition.flags.includes('adult') + // is this label disabled because it's adult only? + const adultDisabled = + adultOnly && !preferences?.moderationPrefs.adultContentEnabled + // are there any reasons we cant configure this label here? + const cantConfigure = isGlobalLabel || adultDisabled + const showConfig = !disabled && (gtPhone || !cantConfigure) + + // adjust the pref based on whether warn is available + let prefAdjusted = pref + if (adultDisabled) { + prefAdjusted = 'hide' + } else if (!canWarn && pref === 'warn') { + prefAdjusted = 'ignore' + } + + // grab localized descriptions of the label and its settings + const currentPrefLabel = useLabelBehaviorDescription( + labelValueDefinition, + prefAdjusted, + ) + const hideLabel = useLabelBehaviorDescription(labelValueDefinition, 'hide') + const warnLabel = useLabelBehaviorDescription(labelValueDefinition, 'warn') + const ignoreLabel = useLabelBehaviorDescription( + labelValueDefinition, + 'ignore', + ) + const globalLabelStrings = useGlobalLabelStrings() + const labelStrings = getLabelStrings( + i18n.locale, + globalLabelStrings, + labelValueDefinition, + ) + + return ( + <View + style={[ + a.flex_row, + a.gap_md, + a.px_lg, + a.py_lg, + a.justify_between, + a.flex_wrap, + ]}> + <View style={[a.gap_xs, a.flex_1]}> + <Text style={[a.font_bold, gtPhone ? a.text_sm : a.text_md]}> + {labelStrings.name} + </Text> + <Text style={[t.atoms.text_contrast_medium, a.leading_snug]}> + {labelStrings.description} + </Text> + + {cantConfigure && ( + <View style={[a.flex_row, a.gap_xs, a.align_center, a.mt_xs]}> + <CircleInfo size="sm" fill={t.atoms.text_contrast_high.color} /> + + <Text + style={[t.atoms.text_contrast_medium, a.font_semibold, a.italic]}> + {adultDisabled ? ( + <Trans>Adult content is disabled.</Trans> + ) : isGlobalLabel ? ( + <Trans> + Configured in{' '} + <InlineLink to="/moderation" style={a.text_sm}> + moderation settings + </InlineLink> + . + </Trans> + ) : null} + </Text> + </View> + )} + </View> + + {showConfig && ( + <View style={[gtPhone ? undefined : a.w_full]}> + {cantConfigure ? ( + <View + style={[ + {minHeight: 35}, + a.px_md, + a.py_md, + a.rounded_sm, + a.border, + t.atoms.border_contrast_low, + ]}> + <Text style={[a.font_bold, t.atoms.text_contrast_low]}> + {currentPrefLabel} + </Text> + </View> + ) : ( + <View style={[{minHeight: 35}]}> + <ToggleButton.Group + label={_( + msg`Configure content filtering setting for category: ${labelStrings.name.toLowerCase()}`, + )} + values={[prefAdjusted]} + onChange={newPref => + mutate({ + label: identifier, + visibility: newPref[0] as LabelPreference, + labelerDid, + }) + }> + <ToggleButton.Button name="ignore" label={ignoreLabel}> + {ignoreLabel} + </ToggleButton.Button> + {canWarn && ( + <ToggleButton.Button name="warn" label={warnLabel}> + {warnLabel} + </ToggleButton.Button> + )} + <ToggleButton.Button name="hide" label={hideLabel}> + {hideLabel} + </ToggleButton.Button> + </ToggleButton.Group> + </View> + )} + </View> + )} + </View> + ) +} diff --git a/src/components/moderation/PostAlerts.tsx b/src/components/moderation/PostAlerts.tsx new file mode 100644 index 000000000..0bfe69678 --- /dev/null +++ b/src/components/moderation/PostAlerts.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import {StyleProp, View, ViewStyle} from 'react-native' +import {ModerationUI, ModerationCause} from '@atproto/api' + +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {getModerationCauseKey} from '#/lib/moderation' + +import {atoms as a} from '#/alf' +import {Button, ButtonText, ButtonIcon} from '#/components/Button' +import { + ModerationDetailsDialog, + useModerationDetailsDialogControl, +} from '#/components/moderation/ModerationDetailsDialog' + +export function PostAlerts({ + modui, + style, +}: { + modui: ModerationUI + includeMute?: boolean + style?: StyleProp<ViewStyle> +}) { + if (!modui.alert && !modui.inform) { + return null + } + + return ( + <View style={[a.flex_col, a.gap_xs, style]}> + <View style={[a.flex_row, a.flex_wrap, a.gap_xs]}> + {modui.alerts.map(cause => ( + <PostLabel key={getModerationCauseKey(cause)} cause={cause} /> + ))} + {modui.informs.map(cause => ( + <PostLabel key={getModerationCauseKey(cause)} cause={cause} /> + ))} + </View> + </View> + ) +} + +function PostLabel({cause}: {cause: ModerationCause}) { + const control = useModerationDetailsDialogControl() + const desc = useModerationCauseDescription(cause) + + return ( + <> + <Button + label={desc.name} + variant="solid" + color="secondary" + size="small" + shape="default" + onPress={() => { + control.open() + }} + style={[a.px_sm, a.py_xs, a.gap_xs]}> + <ButtonIcon icon={desc.icon} position="left" /> + <ButtonText style={[a.text_left, a.leading_snug]}> + {desc.name} + </ButtonText> + </Button> + + <ModerationDetailsDialog control={control} modcause={cause} /> + </> + ) +} diff --git a/src/view/com/util/moderation/PostHider.tsx b/src/components/moderation/PostHider.tsx index ede62e988..464ee2077 100644 --- a/src/view/com/util/moderation/PostHider.tsx +++ b/src/components/moderation/PostHider.tsx @@ -1,45 +1,50 @@ import React, {ComponentProps} from 'react' import {StyleSheet, Pressable, View, ViewStyle, StyleProp} from 'react-native' import {ModerationUI} from '@atproto/api' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {usePalette} from 'lib/hooks/usePalette' -import {Link} from '../Link' -import {Text} from '../text/Text' -import {addStyle} from 'lib/styles' -import {describeModerationCause} from 'lib/moderation' -import {ShieldExclamation} from 'lib/icons' import {useLingui} from '@lingui/react' import {Trans, msg} from '@lingui/macro' -import {useModalControls} from '#/state/modals' + +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {addStyle} from 'lib/styles' + +import {useTheme, atoms as a} from '#/alf' +import { + ModerationDetailsDialog, + useModerationDetailsDialogControl, +} from '#/components/moderation/ModerationDetailsDialog' +import {Text} from '#/components/Typography' +// import {Link} from '#/components/Link' TODO this imposes some styles that screw things up +import {Link} from '#/view/com/util/Link' interface Props extends ComponentProps<typeof Link> { iconSize: number iconStyles: StyleProp<ViewStyle> - moderation: ModerationUI + modui: ModerationUI } export function PostHider({ testID, href, - moderation, + modui, style, children, iconSize, iconStyles, ...props }: Props) { - const pal = usePalette('default') + const t = useTheme() const {_} = useLingui() const [override, setOverride] = React.useState(false) - const {openModal} = useModalControls() + const control = useModerationDetailsDialogControl() + const blur = modui.blurs[0] + const desc = useModerationCauseDescription(blur) - if (!moderation.blur) { + if (!blur) { return ( <Link testID={testID} style={style} href={href} - noFeedback accessible={false} {...props}> {children} @@ -47,12 +52,10 @@ export function PostHider({ ) } - const isMute = ['muted', 'muted-word'].includes(moderation.cause?.type || '') - const desc = describeModerationCause(moderation.cause, 'content') return !override ? ( <Pressable onPress={() => { - if (!moderation.noOverride) { + if (!modui.noOverride) { setOverride(v => !v) } }} @@ -62,49 +65,45 @@ export function PostHider({ } accessibilityLabel="" style={[ - styles.description, + a.flex_row, + a.align_center, + a.gap_sm, + a.py_md, + { + paddingLeft: 6, + paddingRight: 18, + }, override ? {paddingBottom: 0} : undefined, - pal.view, + t.atoms.bg, ]}> + <ModerationDetailsDialog control={control} modcause={blur} /> <Pressable onPress={() => { - openModal({ - name: 'moderation-details', - context: 'content', - moderation, - }) + control.open() }} accessibilityRole="button" accessibilityLabel={_(msg`Learn more about this warning`)} accessibilityHint=""> <View style={[ - pal.viewLight, + t.atoms.bg_contrast_25, + a.align_center, + a.justify_center, { width: iconSize, height: iconSize, borderRadius: iconSize, - alignItems: 'center', - justifyContent: 'center', }, iconStyles, ]}> - {isMute ? ( - <FontAwesomeIcon - icon={['far', 'eye-slash']} - size={14} - color={pal.colors.textLight} - /> - ) : ( - <ShieldExclamation size={14} style={pal.textLight} /> - )} + <desc.icon size="sm" fill={t.atoms.text_contrast_medium.color} /> </View> </Pressable> - <Text type="sm" style={[{flex: 1}, pal.textLight]} numberOfLines={1}> + <Text style={[t.atoms.text_contrast_medium, a.flex_1]} numberOfLines={1}> {desc.name} </Text> - {!moderation.noOverride && ( - <Text type="sm" style={[styles.showBtn, pal.link]}> + {!modui.noOverride && ( + <Text style={[{color: t.palette.primary_500}]}> {override ? <Trans>Hide</Trans> : <Trans>Show</Trans>} </Text> )} @@ -114,26 +113,14 @@ export function PostHider({ testID={testID} style={addStyle(style, styles.child)} href={href} - noFeedback> + accessible={false} + {...props}> {children} </Link> ) } const styles = StyleSheet.create({ - description: { - flexDirection: 'row', - alignItems: 'center', - gap: 4, - paddingVertical: 10, - paddingLeft: 6, - paddingRight: 18, - marginTop: 1, - }, - showBtn: { - marginLeft: 'auto', - alignSelf: 'center', - }, child: { borderWidth: 0, borderTopWidth: 0, diff --git a/src/components/moderation/ProfileHeaderAlerts.tsx b/src/components/moderation/ProfileHeaderAlerts.tsx new file mode 100644 index 000000000..dfc2aa557 --- /dev/null +++ b/src/components/moderation/ProfileHeaderAlerts.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import {StyleProp, View, ViewStyle} from 'react-native' +import {ModerationCause, ModerationDecision} from '@atproto/api' + +import {getModerationCauseKey} from 'lib/moderation' +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' + +import {atoms as a} from '#/alf' +import {Button, ButtonText, ButtonIcon} from '#/components/Button' +import { + ModerationDetailsDialog, + useModerationDetailsDialogControl, +} from '#/components/moderation/ModerationDetailsDialog' + +export function ProfileHeaderAlerts({ + moderation, + style, +}: { + moderation: ModerationDecision + style?: StyleProp<ViewStyle> +}) { + const modui = moderation.ui('profileView') + if (!modui.alert && !modui.inform) { + return null + } + + return ( + <View style={[a.flex_col, a.gap_xs, style]}> + <View style={[a.flex_row, a.flex_wrap, a.gap_xs]}> + {modui.alerts.map(cause => ( + <ProfileLabel key={getModerationCauseKey(cause)} cause={cause} /> + ))} + {modui.informs.map(cause => ( + <ProfileLabel key={getModerationCauseKey(cause)} cause={cause} /> + ))} + </View> + </View> + ) +} + +function ProfileLabel({cause}: {cause: ModerationCause}) { + const control = useModerationDetailsDialogControl() + const desc = useModerationCauseDescription(cause) + + return ( + <> + <Button + label={desc.name} + variant="solid" + color="secondary" + size="small" + shape="default" + onPress={() => { + control.open() + }} + style={[a.px_sm, a.py_xs, a.gap_xs]}> + <ButtonIcon icon={desc.icon} position="left" /> + <ButtonText style={[a.text_left, a.leading_snug]}> + {desc.name} + </ButtonText> + </Button> + + <ModerationDetailsDialog control={control} modcause={cause} /> + </> + ) +} diff --git a/src/components/moderation/ScreenHider.tsx b/src/components/moderation/ScreenHider.tsx new file mode 100644 index 000000000..71ca85a92 --- /dev/null +++ b/src/components/moderation/ScreenHider.tsx @@ -0,0 +1,171 @@ +import React from 'react' +import { + TouchableWithoutFeedback, + StyleProp, + View, + ViewStyle, +} from 'react-native' +import {useNavigation} from '@react-navigation/native' +import {ModerationUI} from '@atproto/api' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {NavigationProp} from 'lib/routes/types' +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' + +import {useTheme, atoms as a} from '#/alf' +import {CenteredView} from '#/view/com/util/Views' +import {Text} from '#/components/Typography' +import {Button, ButtonText} from '#/components/Button' +import { + ModerationDetailsDialog, + useModerationDetailsDialogControl, +} from '#/components/moderation/ModerationDetailsDialog' + +export function ScreenHider({ + testID, + screenDescription, + modui, + style, + containerStyle, + children, +}: React.PropsWithChildren<{ + testID?: string + screenDescription: string + modui: ModerationUI + style?: StyleProp<ViewStyle> + containerStyle?: StyleProp<ViewStyle> +}>) { + const t = useTheme() + const {_} = useLingui() + const [override, setOverride] = React.useState(false) + const navigation = useNavigation<NavigationProp>() + const {isMobile} = useWebMediaQueries() + const control = useModerationDetailsDialogControl() + const blur = modui.blurs[0] + const desc = useModerationCauseDescription(blur) + + if (!blur || override) { + return ( + <View testID={testID} style={style}> + {children} + </View> + ) + } + + const isNoPwi = !!modui.blurs.find( + cause => + cause.type === 'label' && cause.labelDef.id === '!no-unauthenticated', + ) + return ( + <CenteredView + style={[ + a.flex_1, + { + paddingTop: 100, + paddingBottom: 150, + }, + t.atoms.bg, + containerStyle, + ]} + sideBorders> + <View style={[a.align_center, a.mb_md]}> + <View + style={[ + t.atoms.bg_contrast_975, + a.align_center, + a.justify_center, + { + borderRadius: 25, + width: 50, + height: 50, + }, + ]}> + <desc.icon width={24} fill={t.atoms.bg.backgroundColor} /> + </View> + </View> + <Text + style={[ + a.text_4xl, + a.font_semibold, + a.text_center, + a.mb_md, + t.atoms.text, + ]}> + {isNoPwi ? ( + <Trans>Sign-in Required</Trans> + ) : ( + <Trans>Content Warning</Trans> + )} + </Text> + <Text + style={[ + a.text_lg, + a.mb_md, + a.px_lg, + a.text_center, + t.atoms.text_contrast_medium, + ]}> + {isNoPwi ? ( + <Trans> + This account has requested that users sign in to view their profile. + </Trans> + ) : ( + <> + <Trans>This {screenDescription} has been flagged:</Trans> + <Text style={[a.text_lg, a.font_semibold, t.atoms.text, a.ml_xs]}> + {desc.name}.{' '} + </Text> + <TouchableWithoutFeedback + onPress={() => { + control.open() + }} + accessibilityRole="button" + accessibilityLabel={_(msg`Learn more about this warning`)} + accessibilityHint=""> + <Text style={[a.text_lg, {color: t.palette.primary_500}]}> + <Trans>Learn More</Trans> + </Text> + </TouchableWithoutFeedback> + + <ModerationDetailsDialog control={control} modcause={blur} /> + </> + )}{' '} + </Text> + {isMobile && <View style={a.flex_1} />} + <View style={[a.flex_row, a.justify_center, a.my_md, a.gap_md]}> + <Button + variant="solid" + color="primary" + size="large" + style={[a.rounded_full]} + label={_(msg`Go back`)} + onPress={() => { + if (navigation.canGoBack()) { + navigation.goBack() + } else { + navigation.navigate('Home') + } + }}> + <ButtonText> + <Trans>Go back</Trans> + </ButtonText> + </Button> + {!modui.noOverride && ( + <Button + variant="solid" + color="secondary" + size="large" + style={[a.rounded_full]} + label={_(msg`Show anyway`)} + onPress={() => setOverride(v => !v)}> + <ButtonText> + <Trans>Show anyway</Trans> + </ButtonText> + </Button> + )} + </View> + </CenteredView> + ) +} diff --git a/src/lib/__tests__/moderatePost_wrapped.test.ts b/src/lib/__tests__/moderatePost_wrapped.test.ts deleted file mode 100644 index 45566281a..000000000 --- a/src/lib/__tests__/moderatePost_wrapped.test.ts +++ /dev/null @@ -1,692 +0,0 @@ -import {describe, it, expect} from '@jest/globals' -import {RichText} from '@atproto/api' - -import {hasMutedWord} from '../moderatePost_wrapped' - -describe(`hasMutedWord`, () => { - describe(`tags`, () => { - it(`match: outline tag`, () => { - const rt = new RichText({ - text: `This is a post #inlineTag`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: 'outlineTag', targets: ['tag']}], - text: rt.text, - facets: rt.facets, - outlineTags: ['outlineTag'], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: inline tag`, () => { - const rt = new RichText({ - text: `This is a post #inlineTag`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: 'inlineTag', targets: ['tag']}], - text: rt.text, - facets: rt.facets, - outlineTags: ['outlineTag'], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: content target matches inline tag`, () => { - const rt = new RichText({ - text: `This is a post #inlineTag`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: 'inlineTag', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: ['outlineTag'], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`no match: only tag targets`, () => { - const rt = new RichText({ - text: `This is a post`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: 'inlineTag', targets: ['tag']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(false) - }) - }) - - describe(`early exits`, () => { - it(`match: single character 希`, () => { - /** - * @see https://bsky.app/profile/mukuuji.bsky.social/post/3klji4fvsdk2c - */ - const rt = new RichText({ - text: `改善希望ã§ã™`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: '希', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`no match: long muted word, short post`, () => { - const rt = new RichText({ - text: `hey`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: 'politics', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(false) - }) - - it(`match: exact text`, () => { - const rt = new RichText({ - text: `javascript`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: 'javascript', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - }) - - describe(`general content`, () => { - it(`match: word within post`, () => { - const rt = new RichText({ - text: `This is a post about javascript`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: 'javascript', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`no match: partial word`, () => { - const rt = new RichText({ - text: `Use your brain, Eric`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: 'ai', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(false) - }) - - it(`match: multiline`, () => { - const rt = new RichText({ - text: `Use your\n\tbrain, Eric`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: 'brain', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: :)`, () => { - const rt = new RichText({ - text: `So happy :)`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: `:)`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - }) - - describe(`punctuation semi-fuzzy`, () => { - describe(`yay!`, () => { - const rt = new RichText({ - text: `We're federating, yay!`, - }) - rt.detectFacetsWithoutResolution() - - it(`match: yay!`, () => { - const match = hasMutedWord({ - mutedWords: [{value: 'yay!', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: yay`, () => { - const match = hasMutedWord({ - mutedWords: [{value: 'yay', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - }) - - describe(`y!ppee!!`, () => { - const rt = new RichText({ - text: `We're federating, y!ppee!!`, - }) - rt.detectFacetsWithoutResolution() - - it(`match: y!ppee`, () => { - const match = hasMutedWord({ - mutedWords: [{value: 'y!ppee', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - // single exclamation point, source has double - it(`no match: y!ppee!`, () => { - const match = hasMutedWord({ - mutedWords: [{value: 'y!ppee!', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - }) - - describe(`Why so S@assy?`, () => { - const rt = new RichText({ - text: `Why so S@assy?`, - }) - rt.detectFacetsWithoutResolution() - - it(`match: S@assy`, () => { - const match = hasMutedWord({ - mutedWords: [{value: 'S@assy', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: s@assy`, () => { - const match = hasMutedWord({ - mutedWords: [{value: 's@assy', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - }) - - describe(`New York Times`, () => { - const rt = new RichText({ - text: `New York Times`, - }) - rt.detectFacetsWithoutResolution() - - // case insensitive - it(`match: new york times`, () => { - const match = hasMutedWord({ - mutedWords: [{value: 'new york times', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - }) - - describe(`!command`, () => { - const rt = new RichText({ - text: `Idk maybe a bot !command`, - }) - rt.detectFacetsWithoutResolution() - - it(`match: !command`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `!command`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: command`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `command`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`no match: !command`, () => { - const rt = new RichText({ - text: `Idk maybe a bot command`, - }) - rt.detectFacetsWithoutResolution() - - const match = hasMutedWord({ - mutedWords: [{value: `!command`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(false) - }) - }) - - describe(`e/acc`, () => { - const rt = new RichText({ - text: `I'm e/acc pilled`, - }) - rt.detectFacetsWithoutResolution() - - it(`match: e/acc`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `e/acc`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: acc`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `acc`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - }) - - describe(`super-bad`, () => { - const rt = new RichText({ - text: `I'm super-bad`, - }) - rt.detectFacetsWithoutResolution() - - it(`match: super-bad`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `super-bad`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: super`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `super`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: super bad`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `super bad`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: superbad`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `superbad`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(false) - }) - }) - - describe(`idk_what_this_would_be`, () => { - const rt = new RichText({ - text: `Weird post with idk_what_this_would_be`, - }) - rt.detectFacetsWithoutResolution() - - it(`match: idk what this would be`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `idk what this would be`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`no match: idk what this would be for`, () => { - // extra word - const match = hasMutedWord({ - mutedWords: [ - {value: `idk what this would be for`, targets: ['content']}, - ], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(false) - }) - - it(`match: idk`, () => { - // extra word - const match = hasMutedWord({ - mutedWords: [{value: `idk`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: idkwhatthiswouldbe`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `idkwhatthiswouldbe`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(false) - }) - }) - - describe(`parentheses`, () => { - const rt = new RichText({ - text: `Post with context(iykyk)`, - }) - rt.detectFacetsWithoutResolution() - - it(`match: context(iykyk)`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `context(iykyk)`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: context`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `context`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: iykyk`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `iykyk`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: (iykyk)`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `(iykyk)`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - }) - - describe(`🦋`, () => { - const rt = new RichText({ - text: `Post with 🦋`, - }) - rt.detectFacetsWithoutResolution() - - it(`match: 🦋`, () => { - const match = hasMutedWord({ - mutedWords: [{value: `🦋`, targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - }) - }) - - describe(`phrases`, () => { - describe(`I like turtles, or how I learned to stop worrying and love the internet.`, () => { - const rt = new RichText({ - text: `I like turtles, or how I learned to stop worrying and love the internet.`, - }) - rt.detectFacetsWithoutResolution() - - it(`match: stop worrying`, () => { - const match = hasMutedWord({ - mutedWords: [{value: 'stop worrying', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`match: turtles, or how`, () => { - const match = hasMutedWord({ - mutedWords: [{value: 'turtles, or how', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - }) - }) - - describe(`languages without spaces`, () => { - // I love turtles, or how I learned to stop worrying and love the internet - describe(`ç§ã¯ã‚«ãƒ¡ãŒå¥½ãã§ã™ã€ã¾ãŸã¯ã©ã®ã‚ˆã†ã«ã—ã¦å¿ƒé…ã™ã‚‹ã®ã‚’ã‚„ã‚ã¦ã‚¤ãƒ³ã‚¿ãƒ¼ãƒãƒƒãƒˆã‚’æ„›ã™ã‚‹ã‚ˆã†ã«ãªã£ãŸã®ã‹`, () => { - const rt = new RichText({ - text: `ç§ã¯ã‚«ãƒ¡ãŒå¥½ãã§ã™ã€ã¾ãŸã¯ã©ã®ã‚ˆã†ã«ã—ã¦å¿ƒé…ã™ã‚‹ã®ã‚’ã‚„ã‚ã¦ã‚¤ãƒ³ã‚¿ãƒ¼ãƒãƒƒãƒˆã‚’æ„›ã™ã‚‹ã‚ˆã†ã«ãªã£ãŸã®ã‹`, - }) - rt.detectFacetsWithoutResolution() - - // internet - it(`match: インターãƒãƒƒãƒˆ`, () => { - const match = hasMutedWord({ - mutedWords: [{value: 'インターãƒãƒƒãƒˆ', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - languages: ['ja'], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - }) - }) - - describe(`doesn't mute own post`, () => { - it(`does mute if it isn't own post`, () => { - const rt = new RichText({ - text: `Mute words!`, - }) - - const match = hasMutedWord({ - mutedWords: [{value: 'words', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: false, - }) - - expect(match).toBe(true) - }) - - it(`doesn't mute own post when muted word is in text`, () => { - const rt = new RichText({ - text: `Mute words!`, - }) - - const match = hasMutedWord({ - mutedWords: [{value: 'words', targets: ['content']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: true, - }) - - expect(match).toBe(false) - }) - - it(`doesn't mute own post when muted word is in tags`, () => { - const rt = new RichText({ - text: `Mute #words!`, - }) - - const match = hasMutedWord({ - mutedWords: [{value: 'words', targets: ['tags']}], - text: rt.text, - facets: rt.facets, - outlineTags: [], - isOwnPost: true, - }) - - expect(match).toBe(false) - }) - }) -}) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index e86844395..f5a72669a 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -35,6 +35,10 @@ export const MAX_GRAPHEME_LENGTH = 300 // but increasing limit per user feedback export const MAX_ALT_TEXT = 1000 +export function IS_TEST_USER(handle?: string) { + return handle && handle?.endsWith('.test') +} + export function IS_PROD_SERVICE(url?: string) { return url && url !== STAGING_SERVICE && url !== LOCAL_DEV_SERVICE } diff --git a/src/lib/moderatePost_wrapped.ts b/src/lib/moderatePost_wrapped.ts index 9f6fa9c07..0ce01368a 100644 --- a/src/lib/moderatePost_wrapped.ts +++ b/src/lib/moderatePost_wrapped.ts @@ -1,380 +1,30 @@ -import { - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - moderatePost, - AppBskyActorDefs, - AppBskyFeedPost, - AppBskyRichtextFacet, - AppBskyEmbedImages, - AppBskyEmbedExternal, -} from '@atproto/api' +import {moderatePost, BSKY_LABELER_DID} from '@atproto/api' type ModeratePost = typeof moderatePost -type Options = Parameters<ModeratePost>[1] & { - hiddenPosts?: string[] - mutedWords?: AppBskyActorDefs.MutedWord[] -} - -const REGEX = { - LEADING_TRAILING_PUNCTUATION: /(?:^\p{P}+|\p{P}+$)/gu, - ESCAPE: /[[\]{}()*+?.\\^$|\s]/g, - SEPARATORS: /[\/\-\–\—\(\)\[\]\_]+/g, - WORD_BOUNDARY: /[\s\n\t\r\f\v]+?/g, -} - -/** - * List of 2-letter lang codes for languages that either don't use spaces, or - * don't use spaces in a way conducive to word-based filtering. - * - * For these, we use a simple `String.includes` to check for a match. - */ -const LANGUAGE_EXCEPTIONS = [ - 'ja', // Japanese - 'zh', // Chinese - 'ko', // Korean - 'th', // Thai - 'vi', // Vietnamese -] - -export function hasMutedWord({ - mutedWords, - text, - facets, - outlineTags, - languages, - isOwnPost, -}: { - mutedWords: AppBskyActorDefs.MutedWord[] - text: string - facets?: AppBskyRichtextFacet.Main[] - outlineTags?: string[] - languages?: string[] - isOwnPost: boolean -}) { - if (isOwnPost) return false - - const exception = LANGUAGE_EXCEPTIONS.includes(languages?.[0] || '') - const tags = ([] as string[]) - .concat(outlineTags || []) - .concat( - facets - ?.filter(facet => { - return facet.features.find(feature => - AppBskyRichtextFacet.isTag(feature), - ) - }) - .map(t => t.features[0].tag as string) || [], - ) - .map(t => t.toLowerCase()) - - for (const mute of mutedWords) { - const mutedWord = mute.value.toLowerCase() - const postText = text.toLowerCase() - - // `content` applies to tags as well - if (tags.includes(mutedWord)) return true - // rest of the checks are for `content` only - if (!mute.targets.includes('content')) continue - // single character or other exception, has to use includes - if ((mutedWord.length === 1 || exception) && postText.includes(mutedWord)) - return true - // too long - if (mutedWord.length > postText.length) continue - // exact match - if (mutedWord === postText) return true - // any muted phrase with space or punctuation - if (/(?:\s|\p{P})+?/u.test(mutedWord) && postText.includes(mutedWord)) - return true - - // check individual character groups - const words = postText.split(REGEX.WORD_BOUNDARY) - for (const word of words) { - if (word === mutedWord) return true - - // compare word without leading/trailing punctuation, but allow internal - // punctuation (such as `s@ssy`) - const wordTrimmedPunctuation = word.replace( - REGEX.LEADING_TRAILING_PUNCTUATION, - '', - ) - - if (mutedWord === wordTrimmedPunctuation) return true - if (mutedWord.length > wordTrimmedPunctuation.length) continue - - // handle hyphenated, slash separated words, etc - if (REGEX.SEPARATORS.test(wordTrimmedPunctuation)) { - // check against full normalized phrase - const wordNormalizedSeparators = wordTrimmedPunctuation.replace( - REGEX.SEPARATORS, - ' ', - ) - const mutedWordNormalizedSeparators = mutedWord.replace( - REGEX.SEPARATORS, - ' ', - ) - // hyphenated (or other sep) to spaced words - if (wordNormalizedSeparators === mutedWordNormalizedSeparators) - return true - - /* Disabled for now e.g. `super-cool` to `supercool` - const wordNormalizedCompressed = wordNormalizedSeparators.replace( - REGEX.WORD_BOUNDARY, - '', - ) - const mutedWordNormalizedCompressed = - mutedWordNormalizedSeparators.replace(/\s+?/g, '') - // hyphenated (or other sep) to non-hyphenated contiguous word - if (mutedWordNormalizedCompressed === wordNormalizedCompressed) - return true - */ - - // then individual parts of separated phrases/words - const wordParts = wordTrimmedPunctuation.split(REGEX.SEPARATORS) - for (const wp of wordParts) { - // still retain internal punctuation - if (wp === mutedWord) return true - } - } - } - } - - return false -} +type Options = Parameters<ModeratePost>[1] export function moderatePost_wrapped( subject: Parameters<ModeratePost>[0], opts: Options, ) { - const {hiddenPosts = [], mutedWords = [], ...options} = opts - const moderations = moderatePost(subject, options) - const isOwnPost = subject.author.did === opts.userDid - - if (hiddenPosts.includes(subject.uri)) { - moderations.content.filter = true - moderations.content.blur = true - if (!moderations.content.cause) { - moderations.content.cause = { - // @ts-ignore Temporary extension to the moderation system -prf - type: 'post-hidden', - source: {type: 'user'}, - priority: 1, - } - } - } + // HACK + // temporarily translate 'gore' into 'graphic-media' during the transition period + // can remove this in a few months + // -prf + translateOldLabels(subject) - if (AppBskyFeedPost.isRecord(subject.record)) { - let muted = hasMutedWord({ - mutedWords, - text: subject.record.text, - facets: subject.record.facets || [], - outlineTags: subject.record.tags || [], - languages: subject.record.langs, - isOwnPost, - }) - - if ( - subject.record.embed && - AppBskyEmbedImages.isMain(subject.record.embed) - ) { - for (const image of subject.record.embed.images) { - muted = - muted || - hasMutedWord({ - mutedWords, - text: image.alt, - facets: [], - outlineTags: [], - languages: subject.record.langs, - isOwnPost, - }) - } - } - - if (muted) { - moderations.content.filter = true - moderations.content.blur = true - if (!moderations.content.cause) { - moderations.content.cause = { - // @ts-ignore Temporary extension to the moderation system -prf - type: 'muted-word', - source: {type: 'user'}, - priority: 1, - } - } - } - } - - if (subject.embed) { - let embedHidden = false - let embedMuted = false - let externalMuted = false - - if (AppBskyEmbedRecord.isViewRecord(subject.embed.record)) { - embedHidden = hiddenPosts.includes(subject.embed.record.uri) - } - if ( - AppBskyEmbedRecordWithMedia.isView(subject.embed) && - AppBskyEmbedRecord.isViewRecord(subject.embed.record.record) - ) { - embedHidden = hiddenPosts.includes(subject.embed.record.record.uri) - } - - if (AppBskyEmbedRecord.isViewRecord(subject.embed.record)) { - if (AppBskyFeedPost.isRecord(subject.embed.record.value)) { - const embeddedPost = subject.embed.record.value - - embedMuted = - embedMuted || - hasMutedWord({ - mutedWords, - text: embeddedPost.text, - facets: embeddedPost.facets, - outlineTags: embeddedPost.tags, - languages: embeddedPost.langs, - isOwnPost, - }) - - if (AppBskyEmbedImages.isMain(embeddedPost.embed)) { - for (const image of embeddedPost.embed.images) { - embedMuted = - embedMuted || - hasMutedWord({ - mutedWords, - text: image.alt, - facets: [], - outlineTags: [], - languages: embeddedPost.langs, - isOwnPost, - }) - } - } - - if (AppBskyEmbedExternal.isMain(embeddedPost.embed)) { - const {external} = embeddedPost.embed - - embedMuted = - embedMuted || - hasMutedWord({ - mutedWords, - text: external.title + ' ' + external.description, - facets: [], - outlineTags: [], - languages: [], - isOwnPost, - }) - } - - if (AppBskyEmbedRecordWithMedia.isMain(embeddedPost.embed)) { - if (AppBskyEmbedExternal.isMain(embeddedPost.embed.media)) { - const {external} = embeddedPost.embed.media - - embedMuted = - embedMuted || - hasMutedWord({ - mutedWords, - text: external.title + ' ' + external.description, - facets: [], - outlineTags: [], - languages: [], - isOwnPost, - }) - } - - if (AppBskyEmbedImages.isMain(embeddedPost.embed.media)) { - for (const image of embeddedPost.embed.media.images) { - embedMuted = - embedMuted || - hasMutedWord({ - mutedWords, - text: image.alt, - facets: [], - outlineTags: [], - languages: AppBskyFeedPost.isRecord(embeddedPost.record) - ? embeddedPost.langs - : [], - isOwnPost, - }) - } - } - } - } - } - - if (AppBskyEmbedExternal.isView(subject.embed)) { - const {external} = subject.embed - - externalMuted = - externalMuted || - hasMutedWord({ - mutedWords, - text: external.title + ' ' + external.description, - facets: [], - outlineTags: [], - languages: [], - isOwnPost, - }) - } - - if ( - AppBskyEmbedRecordWithMedia.isView(subject.embed) && - AppBskyEmbedRecord.isViewRecord(subject.embed.record.record) - ) { - if (AppBskyFeedPost.isRecord(subject.embed.record.record.value)) { - const post = subject.embed.record.record.value - embedMuted = - embedMuted || - hasMutedWord({ - mutedWords, - text: post.text, - facets: post.facets, - outlineTags: post.tags, - languages: post.langs, - isOwnPost, - }) - } - - if (AppBskyEmbedImages.isView(subject.embed.media)) { - for (const image of subject.embed.media.images) { - embedMuted = - embedMuted || - hasMutedWord({ - mutedWords, - text: image.alt, - facets: [], - outlineTags: [], - languages: AppBskyFeedPost.isRecord(subject.record) - ? subject.record.langs - : [], - isOwnPost, - }) - } - } - } + return moderatePost(subject, opts) +} - if (embedHidden) { - moderations.embed.filter = true - moderations.embed.blur = true - if (!moderations.embed.cause) { - moderations.embed.cause = { - // @ts-ignore Temporary extension to the moderation system -prf - type: 'post-hidden', - source: {type: 'user'}, - priority: 1, - } - } - } else if (externalMuted || embedMuted) { - moderations.content.filter = true - moderations.content.blur = true - if (!moderations.content.cause) { - moderations.content.cause = { - // @ts-ignore Temporary extension to the moderation system -prf - type: 'muted-word', - source: {type: 'user'}, - priority: 1, - } +function translateOldLabels(subject: Parameters<ModeratePost>[0]) { + if (subject.labels) { + for (const label of subject.labels) { + if ( + label.val === 'gore' && + (!label.src || label.src === BSKY_LABELER_DID) + ) { + label.val = 'graphic-media' } } } - - return moderations } diff --git a/src/lib/moderation.ts b/src/lib/moderation.ts index b6ebb47a0..4105c2c2d 100644 --- a/src/lib/moderation.ts +++ b/src/lib/moderation.ts @@ -1,149 +1,81 @@ -import {ModerationCause, ProfileModeration, PostModeration} from '@atproto/api' +import { + ModerationCause, + ModerationUI, + InterpretedLabelValueDefinition, + LABELS, + AppBskyLabelerDefs, + BskyAgent, + ModerationOpts, +} from '@atproto/api' -export interface ModerationCauseDescription { - name: string - description: string -} +import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {sanitizeHandle} from '#/lib/strings/handles' -export function describeModerationCause( - cause: ModerationCause | undefined, - context: 'account' | 'content', -): ModerationCauseDescription { - if (!cause) { - return { - name: 'Content Warning', - description: - 'Moderator has chosen to set a general warning on the content.', - } - } - if (cause.type === 'blocking') { - if (cause.source.type === 'list') { - return { - name: `User Blocked by "${cause.source.list.name}"`, - description: - 'You have blocked this user. You cannot view their content.', - } - } else { - return { - name: 'User Blocked', - description: - 'You have blocked this user. You cannot view their content.', - } - } - } - if (cause.type === 'blocked-by') { - return { - name: 'User Blocking You', - description: 'This user has blocked you. You cannot view their content.', - } - } - if (cause.type === 'block-other') { - return { - name: 'Content Not Available', - description: - 'This content is not available because one of the users involved has blocked the other.', - } - } - if (cause.type === 'muted') { - if (cause.source.type === 'list') { - return { - name: - context === 'account' - ? `Muted by "${cause.source.list.name}"` - : `Post by muted user ("${cause.source.list.name}")`, - description: 'You have muted this user', - } - } else { - return { - name: context === 'account' ? 'Muted User' : 'Post by muted user', - description: 'You have muted this user', - } - } - } - // @ts-ignore Temporary extension to the moderation system -prf - if (cause.type === 'post-hidden') { - return { - name: 'Post Hidden by You', - description: 'You have hidden this post', - } - } - // @ts-ignore Temporary extension to the moderation system -prf - if (cause.type === 'muted-word') { - return { - name: 'Post hidden by muted word', - description: `You've chosen to hide a word or tag within this post.`, - } +export function getModerationCauseKey(cause: ModerationCause): string { + const source = + cause.source.type === 'labeler' + ? cause.source.did + : cause.source.type === 'list' + ? cause.source.list.uri + : 'user' + if (cause.type === 'label') { + return `label:${cause.label.val}:${source}` } - return cause.labelDef.strings[context].en + return `${cause.type}:${source}` } -export function getProfileModerationCauses( - moderation: ProfileModeration, -): ModerationCause[] { - /* - Gather everything on profile and account that blurs or alerts - */ - return [ - moderation.decisions.profile.cause, - ...moderation.decisions.profile.additionalCauses, - moderation.decisions.account.cause, - ...moderation.decisions.account.additionalCauses, - ].filter(cause => { - if (!cause) { - return false - } - if (cause?.type === 'label') { - if ( - cause.labelDef.onwarn === 'blur' || - cause.labelDef.onwarn === 'alert' - ) { - return true - } else { - return false - } - } - return true - }) as ModerationCause[] +export function isJustAMute(modui: ModerationUI): boolean { + return modui.filters.length === 1 && modui.filters[0].type === 'muted' } -export function isPostMediaBlurred( - decisions: PostModeration['decisions'], -): boolean { - return decisions.post.blurMedia +export function getLabelingServiceTitle({ + displayName, + handle, +}: { + displayName?: string + handle: string +}) { + return displayName + ? sanitizeDisplayName(displayName) + : sanitizeHandle(handle, '@') } -export function isQuoteBlurred( - decisions: PostModeration['decisions'], -): boolean { - return ( - decisions.quote?.blur || - decisions.quote?.blurMedia || - decisions.quote?.filter || - decisions.quotedAccount?.blur || - decisions.quotedAccount?.filter || - false - ) +export function lookupLabelValueDefinition( + labelValue: string, + customDefs: InterpretedLabelValueDefinition[] | undefined, +): InterpretedLabelValueDefinition | undefined { + let def + if (!labelValue.startsWith('!') && customDefs) { + def = customDefs.find(d => d.identifier === labelValue) + } + if (!def) { + def = LABELS[labelValue as keyof typeof LABELS] + } + return def } -export function isCauseALabelOnUri( - cause: ModerationCause | undefined, - uri: string, +export function isAppLabeler( + labeler: + | string + | AppBskyLabelerDefs.LabelerView + | AppBskyLabelerDefs.LabelerViewDetailed, ): boolean { - if (cause?.type !== 'label') { - return false + if (typeof labeler === 'string') { + return BskyAgent.appLabelers.includes(labeler) } - return cause.label.uri === uri + return BskyAgent.appLabelers.includes(labeler.creator.did) } -export function getModerationCauseKey(cause: ModerationCause): string { - const source = - cause.source.type === 'labeler' - ? cause.source.labeler.did - : cause.source.type === 'list' - ? cause.source.list.uri - : 'user' - if (cause.type === 'label') { - return `label:${cause.label.val}:${source}` +export function isLabelerSubscribed( + labeler: + | string + | AppBskyLabelerDefs.LabelerView + | AppBskyLabelerDefs.LabelerViewDetailed, + modOpts: ModerationOpts, +) { + labeler = typeof labeler === 'string' ? labeler : labeler.creator.did + if (isAppLabeler(labeler)) { + return true } - return `${cause.type}:${source}` + return modOpts.prefs.labelers.find(l => l.did === labeler) } diff --git a/src/lib/moderation/useGlobalLabelStrings.ts b/src/lib/moderation/useGlobalLabelStrings.ts new file mode 100644 index 000000000..1c5a48231 --- /dev/null +++ b/src/lib/moderation/useGlobalLabelStrings.ts @@ -0,0 +1,52 @@ +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useMemo} from 'react' + +export type GlobalLabelStrings = Record< + string, + { + name: string + description: string + } +> + +export function useGlobalLabelStrings(): GlobalLabelStrings { + const {_} = useLingui() + return useMemo( + () => ({ + '!hide': { + name: _(msg`Content Blocked`), + description: _(msg`This content has been hidden by the moderators.`), + }, + '!warn': { + name: _(msg`Content Warning`), + description: _( + msg`This content has received a general warning from moderators.`, + ), + }, + '!no-unauthenticated': { + name: _(msg`Sign-in Required`), + description: _( + msg`This user has requested that their content only be shown to signed-in users.`, + ), + }, + porn: { + name: _(msg`Pornography`), + description: _(msg`Explicit sexual images.`), + }, + sexual: { + name: _(msg`Sexually Suggestive`), + description: _(msg`Does not include nudity.`), + }, + nudity: { + name: _(msg`Non-sexual Nudity`), + description: _(msg`E.g. artistic nudes.`), + }, + 'graphic-media': { + name: _(msg`Graphic Media`), + description: _(msg`Explicit or potentially disturbing media.`), + }, + }), + [_], + ) +} diff --git a/src/lib/moderation/useLabelBehaviorDescription.ts b/src/lib/moderation/useLabelBehaviorDescription.ts new file mode 100644 index 000000000..0250c1bc8 --- /dev/null +++ b/src/lib/moderation/useLabelBehaviorDescription.ts @@ -0,0 +1,70 @@ +import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api' +import {useLingui} from '@lingui/react' +import {msg} from '@lingui/macro' + +export function useLabelBehaviorDescription( + labelValueDef: InterpretedLabelValueDefinition, + pref: LabelPreference, +) { + const {_} = useLingui() + if (pref === 'ignore') { + return _(msg`Off`) + } + if (labelValueDef.blurs === 'content' || labelValueDef.blurs === 'media') { + if (pref === 'hide') { + return _(msg`Hide`) + } + return _(msg`Warn`) + } else if (labelValueDef.severity === 'alert') { + if (pref === 'hide') { + return _(msg`Hide`) + } + return _(msg`Warn`) + } else if (labelValueDef.severity === 'inform') { + if (pref === 'hide') { + return _(msg`Hide`) + } + return _(msg`Show badge`) + } else { + if (pref === 'hide') { + return _(msg`Hide`) + } + return _(msg`Disabled`) + } +} + +export function useLabelLongBehaviorDescription( + labelValueDef: InterpretedLabelValueDefinition, + pref: LabelPreference, +) { + const {_} = useLingui() + if (pref === 'ignore') { + return _(msg`Disabled`) + } + if (labelValueDef.blurs === 'content') { + if (pref === 'hide') { + return _(msg`Warn content and filter from feeds`) + } + return _(msg`Warn content`) + } else if (labelValueDef.blurs === 'media') { + if (pref === 'hide') { + return _(msg`Blur images and filter from feeds`) + } + return _(msg`Blur images`) + } else if (labelValueDef.severity === 'alert') { + if (pref === 'hide') { + return _(msg`Show warning and filter from feeds`) + } + return _(msg`Show warning`) + } else if (labelValueDef.severity === 'inform') { + if (pref === 'hide') { + return _(msg`Show badge and filter from feeds`) + } + return _(msg`Show badge`) + } else { + if (pref === 'hide') { + return _(msg`Filter from feeds`) + } + return _(msg`Disabled`) + } +} diff --git a/src/lib/moderation/useLabelInfo.ts b/src/lib/moderation/useLabelInfo.ts new file mode 100644 index 000000000..b1cffe1e7 --- /dev/null +++ b/src/lib/moderation/useLabelInfo.ts @@ -0,0 +1,100 @@ +import { + ComAtprotoLabelDefs, + AppBskyLabelerDefs, + LABELS, + interpretLabelValueDefinition, + InterpretedLabelValueDefinition, +} from '@atproto/api' +import {useLingui} from '@lingui/react' +import * as bcp47Match from 'bcp-47-match' + +import { + GlobalLabelStrings, + useGlobalLabelStrings, +} from '#/lib/moderation/useGlobalLabelStrings' +import {useLabelDefinitions} from '#/state/preferences' + +export interface LabelInfo { + label: ComAtprotoLabelDefs.Label + def: InterpretedLabelValueDefinition + strings: ComAtprotoLabelDefs.LabelValueDefinitionStrings + labeler: AppBskyLabelerDefs.LabelerViewDetailed | undefined +} + +export function useLabelInfo(label: ComAtprotoLabelDefs.Label): LabelInfo { + const {i18n} = useLingui() + const {labelDefs, labelers} = useLabelDefinitions() + const globalLabelStrings = useGlobalLabelStrings() + const def = getDefinition(labelDefs, label) + return { + label, + def, + strings: getLabelStrings(i18n.locale, globalLabelStrings, def), + labeler: labelers.find(labeler => label.src === labeler.creator.did), + } +} + +export function getDefinition( + labelDefs: Record<string, InterpretedLabelValueDefinition[]>, + label: ComAtprotoLabelDefs.Label, +): InterpretedLabelValueDefinition { + // check local definitions + const customDef = + !label.val.startsWith('!') && + labelDefs[label.src]?.find( + def => def.identifier === label.val && def.definedBy === label.src, + ) + if (customDef) { + return customDef + } + + // check global definitions + const globalDef = LABELS[label.val as keyof typeof LABELS] + if (globalDef) { + return globalDef + } + + // fallback to a noop definition + return interpretLabelValueDefinition( + { + identifier: label.val, + severity: 'none', + blurs: 'none', + defaultSetting: 'ignore', + locales: [], + }, + label.src, + ) +} + +export function getLabelStrings( + locale: string, + globalLabelStrings: GlobalLabelStrings, + def: InterpretedLabelValueDefinition, +): ComAtprotoLabelDefs.LabelValueDefinitionStrings { + if (!def.definedBy) { + // global definition, look up strings + if (def.identifier in globalLabelStrings) { + return globalLabelStrings[ + def.identifier + ] as ComAtprotoLabelDefs.LabelValueDefinitionStrings + } + } else { + // try to find locale match in the definition's strings + const localeMatch = def.locales.find( + strings => bcp47Match.basicFilter(locale, strings.lang).length > 0, + ) + if (localeMatch) { + return localeMatch + } + // fall back to the zero item if no match + if (def.locales[0]) { + return def.locales[0] + } + } + return { + lang: locale, + name: def.identifier, + description: `Labeled "${def.identifier}"`, + } +} diff --git a/src/lib/moderation/useModerationCauseDescription.ts b/src/lib/moderation/useModerationCauseDescription.ts new file mode 100644 index 000000000..46771e958 --- /dev/null +++ b/src/lib/moderation/useModerationCauseDescription.ts @@ -0,0 +1,146 @@ +import React from 'react' +import { + BSKY_LABELER_DID, + ModerationCause, + ModerationCauseSource, +} from '@atproto/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {getDefinition, getLabelStrings} from './useLabelInfo' +import {useLabelDefinitions} from '#/state/preferences' +import {useGlobalLabelStrings} from './useGlobalLabelStrings' + +import {Props as SVGIconProps} from '#/components/icons/common' +import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' +import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign' + +export interface ModerationCauseDescription { + icon: React.ComponentType<SVGIconProps> + name: string + description: string + source?: string + sourceType?: ModerationCauseSource['type'] +} + +export function useModerationCauseDescription( + cause: ModerationCause | undefined, +): ModerationCauseDescription { + const {_, i18n} = useLingui() + const {labelDefs, labelers} = useLabelDefinitions() + const globalLabelStrings = useGlobalLabelStrings() + + return React.useMemo(() => { + if (!cause) { + return { + icon: Warning, + name: _(msg`Content Warning`), + description: _( + msg`Moderator has chosen to set a general warning on the content.`, + ), + } + } + if (cause.type === 'blocking') { + if (cause.source.type === 'list') { + return { + icon: CircleBanSign, + name: _(msg`User Blocked by "${cause.source.list.name}"`), + description: _( + msg`You have blocked this user. You cannot view their content.`, + ), + } + } else { + return { + icon: CircleBanSign, + name: _(msg`User Blocked`), + description: _( + msg`You have blocked this user. You cannot view their content.`, + ), + } + } + } + if (cause.type === 'blocked-by') { + return { + icon: CircleBanSign, + name: _(msg`User Blocking You`), + description: _( + msg`This user has blocked you. You cannot view their content.`, + ), + } + } + if (cause.type === 'block-other') { + return { + icon: CircleBanSign, + name: _(msg`Content Not Available`), + description: _( + msg`This content is not available because one of the users involved has blocked the other.`, + ), + } + } + if (cause.type === 'muted') { + if (cause.source.type === 'list') { + return { + icon: EyeSlash, + name: _(msg`Muted by "${cause.source.list.name}"`), + description: _(msg`You have muted this user`), + } + } else { + return { + icon: EyeSlash, + name: _(msg`Account Muted`), + description: _(msg`You have muted this account.`), + } + } + } + if (cause.type === 'mute-word') { + return { + icon: EyeSlash, + name: _(msg`Post Hidden by Muted Word`), + description: _( + msg`You've chosen to hide a word or tag within this post.`, + ), + } + } + if (cause.type === 'hidden') { + return { + icon: EyeSlash, + name: _(msg`Post Hidden by You`), + description: _(msg`You have hidden this post`), + } + } + if (cause.type === 'label') { + const def = cause.labelDef || getDefinition(labelDefs, cause.label) + const strings = getLabelStrings(i18n.locale, globalLabelStrings, def) + const labeler = labelers.find(l => l.creator.did === cause.label.src) + let source = + labeler?.creator.displayName || + (labeler?.creator.handle ? '@' + labeler?.creator.handle : undefined) + if (!source) { + if (cause.label.src === BSKY_LABELER_DID) { + source = 'Bluesky Moderation' + } else { + source = cause.label.src + } + } + return { + icon: + def.identifier === '!no-unauthenticated' + ? EyeSlash + : def.severity === 'alert' + ? Warning + : CircleInfo, + name: strings.name, + description: strings.description, + source, + sourceType: cause.source.type, + } + } + // should never happen + return { + icon: CircleInfo, + name: '', + description: ``, + } + }, [labelDefs, labelers, globalLabelStrings, cause, _, i18n.locale]) +} diff --git a/src/lib/moderation/useReportOptions.ts b/src/lib/moderation/useReportOptions.ts new file mode 100644 index 000000000..e00170594 --- /dev/null +++ b/src/lib/moderation/useReportOptions.ts @@ -0,0 +1,94 @@ +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useMemo} from 'react' +import {ComAtprotoModerationDefs} from '@atproto/api' + +export interface ReportOption { + reason: string + title: string + description: string +} + +interface ReportOptions { + account: ReportOption[] + post: ReportOption[] + list: ReportOption[] + feedgen: ReportOption[] + other: ReportOption[] +} + +export function useReportOptions(): ReportOptions { + const {_} = useLingui() + return useMemo(() => { + const other = { + reason: ComAtprotoModerationDefs.REASONOTHER, + title: _(msg`Other`), + description: _(msg`An issue not included in these options`), + } + const common = [ + { + reason: ComAtprotoModerationDefs.REASONRUDE, + title: _(msg`Anti-Social Behavior`), + description: _(msg`Harassment, trolling, or intolerance`), + }, + { + reason: ComAtprotoModerationDefs.REASONVIOLATION, + title: _(msg`Illegal and Urgent`), + description: _(msg`Glaring violations of law or terms of service`), + }, + other, + ] + return { + account: [ + { + reason: ComAtprotoModerationDefs.REASONMISLEADING, + title: _(msg`Misleading Account`), + description: _( + msg`Impersonation or false claims about identity or affiliation`, + ), + }, + { + reason: ComAtprotoModerationDefs.REASONSPAM, + title: _(msg`Frequently Posts Unwanted Content`), + description: _(msg`Spam; excessive mentions or replies`), + }, + { + reason: ComAtprotoModerationDefs.REASONVIOLATION, + title: _(msg`Name or Description Violates Community Standards`), + description: _(msg`Terms used violate community standards`), + }, + other, + ], + post: [ + { + reason: ComAtprotoModerationDefs.REASONSPAM, + title: _(msg`Spam`), + description: _(msg`Excessive mentions or replies`), + }, + { + reason: ComAtprotoModerationDefs.REASONSEXUAL, + title: _(msg`Unwanted Sexual Content`), + description: _(msg`Nudity or pornography not labeled as such`), + }, + ...common, + ], + list: [ + { + reason: ComAtprotoModerationDefs.REASONVIOLATION, + title: _(msg`Name or Description Violates Community Standards`), + description: _(msg`Terms used violate community standards`), + }, + ...common, + ], + feedgen: [ + { + reason: ComAtprotoModerationDefs.REASONVIOLATION, + title: _(msg`Name or Description Violates Community Standards`), + description: _(msg`Terms used violate community standards`), + }, + ...common, + ], + other: common, + } + }, [_]) +} diff --git a/src/lib/notifications/notifications.ts b/src/lib/notifications/notifications.ts index 62d0bfc4b..e811f690e 100644 --- a/src/lib/notifications/notifications.ts +++ b/src/lib/notifications/notifications.ts @@ -7,6 +7,7 @@ import {logger} from '#/logger' import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed' import {truncateAndInvalidate} from '#/state/queries/util' import {SessionAccount, getAgent} from '#/state/session' +import {logEvent} from '../statsig/statsig' const SERVICE_DID = (serviceUrl?: string) => serviceUrl?.includes('staging') @@ -123,6 +124,7 @@ export function init(queryClient: QueryClient) { logger.DebugContext.notifications, ) track('Notificatons:OpenApp') + logEvent('notifications:openApp', {}) truncateAndInvalidate(queryClient, RQKEY_NOTIFS()) resetToTab('NotificationsTab') // open notifications tab } diff --git a/src/lib/react-query.ts b/src/lib/react-query.ts index 7fe3fe7a4..d6cd3c54b 100644 --- a/src/lib/react-query.ts +++ b/src/lib/react-query.ts @@ -1,7 +1,14 @@ import {AppState, AppStateStatus} from 'react-native' import {QueryClient, focusManager} from '@tanstack/react-query' +import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' +import AsyncStorage from '@react-native-async-storage/async-storage' +import {PersistQueryClientProviderProps} from '@tanstack/react-query-persist-client' + import {isNative} from '#/platform/detection' +// any query keys in this array will be persisted to AsyncStorage +const STORED_CACHE_QUERY_KEYS = ['labelers-detailed-info'] + focusManager.setEventListener(onFocus => { if (isNative) { const subscription = AppState.addEventListener( @@ -48,3 +55,16 @@ export const queryClient = new QueryClient({ }, }, }) + +export const asyncStoragePersister = createAsyncStoragePersister({ + storage: AsyncStorage, + key: 'queryCache', +}) + +export const dehydrateOptions: PersistQueryClientProviderProps['persistOptions']['dehydrateOptions'] = + { + shouldDehydrateMutation: (_: any) => false, + shouldDehydrateQuery: query => { + return STORED_CACHE_QUERY_KEYS.includes(String(query.queryKey[0])) + }, + } diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 6756a62a6..95af2f237 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -21,7 +21,9 @@ export type CommonNavigatorParams = { PostRepostedBy: {name: string; rkey: string} ProfileFeed: {name: string; rkey: string} ProfileFeedLikedBy: {name: string; rkey: string} + ProfileLabelerLikedBy: {name: string} Debug: undefined + DebugMod: undefined Log: undefined Support: undefined PrivacyPolicy: undefined diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index fa7e597fb..b91a15ecb 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -2,6 +2,9 @@ export type LogEvents = { init: { initMs: number } + 'notifications:openApp': {} + 'state:background': {} + 'state:foreground': {} 'feed:endReached': { feedType: string itemCount: number diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index 5745d204a..3abec5c4f 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -1,9 +1,11 @@ import React from 'react' +import {Platform} from 'react-native' import { Statsig, StatsigProvider, useGate as useStatsigGate, } from 'statsig-react-native-expo' +import {AppState, AppStateStatus} from 'react-native' import {useSession} from '../../state/session' import {sha256} from 'js-sha256' import {LogEvents} from './events' @@ -58,9 +60,25 @@ function toStatsigUser(did: string | undefined) { if (did) { userID = sha256(did) } - return {userID} + return { + userID, + platform: Platform.OS, + } } +let lastState: AppStateStatus = AppState.currentState +AppState.addEventListener('change', (state: AppStateStatus) => { + if (state === lastState) { + return + } + lastState = state + if (state === 'active') { + logEvent('state:foreground', {}) + } else { + logEvent('state:background', {}) + } +}) + export function Provider({children}: {children: React.ReactNode}) { const {currentAccount} = useSession() const currentStatsigUser = React.useMemo( diff --git a/src/lib/strings/display-names.ts b/src/lib/strings/display-names.ts index 75383dd4f..e0f23fa2c 100644 --- a/src/lib/strings/display-names.ts +++ b/src/lib/strings/display-names.ts @@ -1,5 +1,4 @@ import {ModerationUI} from '@atproto/api' -import {describeModerationCause} from '../moderation' // \u2705 = ✅ // \u2713 = ✓ @@ -14,7 +13,7 @@ export function sanitizeDisplayName( moderation?: ModerationUI, ): string { if (moderation?.blur) { - return `âš ${describeModerationCause(moderation.cause, 'account').name}` + return '' } if (typeof str === 'string') { return str.replace(CHECK_MARKS_RE, '').replace(CONTROL_CHARS_RE, '').trim() diff --git a/src/lib/strings/embed-player.ts b/src/lib/strings/embed-player.ts index 1cf3b1293..ee7328478 100644 --- a/src/lib/strings/embed-player.ts +++ b/src/lib/strings/embed-player.ts @@ -2,6 +2,15 @@ import {Dimensions} from 'react-native' import {isWeb} from 'platform/detection' const {height: SCREEN_HEIGHT} = Dimensions.get('window') +const IFRAME_HOST = isWeb + ? // @ts-ignore only for web + window.location.host === 'localhost:8100' + ? 'http://localhost:8100' + : 'https://bsky.app' + : __DEV__ && !process.env.JEST_WORKER_ID + ? 'http://localhost:8100' + : 'https://bsky.app' + export const embedPlayerSources = [ 'youtube', 'youtubeShorts', @@ -74,7 +83,7 @@ export function parseEmbedPlayerFromUrl( return { type: 'youtube_video', source: 'youtube', - playerUri: `https://bsky.app/iframe/youtube.html?videoId=${videoId}&start=${seek}`, + playerUri: `${IFRAME_HOST}/iframe/youtube.html?videoId=${videoId}&start=${seek}`, } } } @@ -93,7 +102,7 @@ export function parseEmbedPlayerFromUrl( type: page === 'shorts' ? 'youtube_short' : 'youtube_video', source: page === 'shorts' ? 'youtubeShorts' : 'youtube', hideDetails: page === 'shorts' ? true : undefined, - playerUri: `https://bsky.app/iframe/youtube.html?videoId=${videoId}&start=${seek}`, + playerUri: `${IFRAME_HOST}/iframe/youtube.html?videoId=${videoId}&start=${seek}`, } } } diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts index 820311e4e..70a2b7069 100644 --- a/src/lib/strings/url-helpers.ts +++ b/src/lib/strings/url-helpers.ts @@ -4,6 +4,23 @@ import TLDs from 'tlds' import psl from 'psl' export const BSKY_APP_HOST = 'https://bsky.app' +const BSKY_TRUSTED_HOSTS = [ + 'bsky.app', + 'bsky.social', + 'blueskyweb.xyz', + 'blueskyweb.zendesk.com', + ...(__DEV__ ? ['localhost:19006', 'localhost:8100'] : []), +] + +/* + * This will allow any BSKY_TRUSTED_HOSTS value by itself or with a subdomain. + * It will also allow relative paths like /profile as well as #. + */ +const TRUSTED_REGEX = new RegExp( + `^(http(s)?://(([\\w-]+\\.)?${BSKY_TRUSTED_HOSTS.join( + '|([\\w-]+\\.)?', + )})|/|#)`, +) export function isValidDomain(str: string): boolean { return !!TLDs.find(tld => { @@ -86,6 +103,10 @@ export function isExternalUrl(url: string): boolean { return external || rss } +export function isTrustedUrl(url: string): boolean { + return TRUSTED_REGEX.test(url) +} + export function isBskyPostUrl(url: string): boolean { if (isBskyAppUrl(url)) { try { @@ -163,8 +184,8 @@ export function feedUriToHref(url: string): string { export function linkRequiresWarning(uri: string, label: string) { const labelDomain = labelToDomain(label) - // If the uri started with a / we know it is internal. - if (isRelativeUrl(uri)) { + // We should trust any relative URL or a # since we know it links to internal content + if (isRelativeUrl(uri) || uri === '#') { return false } @@ -176,18 +197,11 @@ export function linkRequiresWarning(uri: string, label: string) { } const host = urip.hostname.toLowerCase() - // Hosts that end with bsky.app or bsky.social should be trusted by default. - if ( - host.endsWith('bsky.app') || - host.endsWith('bsky.social') || - host.endsWith('blueskyweb.xyz') - ) { - // if this is a link to internal content, - // warn if it represents itself as a URL to another app + if (isTrustedUrl(uri)) { + // if this is a link to internal content, warn if it represents itself as a URL to another app return !!labelDomain && labelDomain !== host && isPossiblyAUrl(labelDomain) } else { - // if this is a link to external content, - // warn if the label doesnt match the target + // if this is a link to external content, warn if the label doesnt match the target if (!labelDomain) { return true } diff --git a/src/lib/themes.ts b/src/lib/themes.ts index bd75aabea..6fada40a7 100644 --- a/src/lib/themes.ts +++ b/src/lib/themes.ts @@ -9,7 +9,7 @@ export const defaultTheme: Theme = { palette: { default: { background: lightPalette.white, - backgroundLight: lightPalette.contrast_50, + backgroundLight: lightPalette.contrast_25, text: lightPalette.black, textLight: lightPalette.contrast_700, textInverted: lightPalette.white, diff --git a/src/locale/helpers.ts b/src/locale/helpers.ts index c73242e70..d07b95d93 100644 --- a/src/locale/helpers.ts +++ b/src/locale/helpers.ts @@ -122,6 +122,8 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage { return AppLanguage.de case 'es': return AppLanguage.es + case 'fi': + return AppLanguage.fi case 'fr': return AppLanguage.fr case 'hi': diff --git a/src/locale/i18n.ts b/src/locale/i18n.ts index e8addb0b4..a1e950947 100644 --- a/src/locale/i18n.ts +++ b/src/locale/i18n.ts @@ -6,6 +6,7 @@ import {messages as messagesEn} from '#/locale/locales/en/messages' import {messages as messagesDe} from '#/locale/locales/de/messages' import {messages as messagesId} from '#/locale/locales/id/messages' import {messages as messagesEs} from '#/locale/locales/es/messages' +import {messages as messagesFi} from '#/locale/locales/fi/messages' import {messages as messagesFr} from '#/locale/locales/fr/messages' import {messages as messagesHi} from '#/locale/locales/hi/messages' import {messages as messagesJa} from '#/locale/locales/ja/messages' @@ -32,6 +33,10 @@ export async function dynamicActivate(locale: AppLanguage) { i18n.loadAndActivate({locale, messages: messagesEs}) break } + case AppLanguage.fi: { + i18n.loadAndActivate({locale, messages: messagesFi}) + break + } case AppLanguage.fr: { i18n.loadAndActivate({locale, messages: messagesFr}) break diff --git a/src/locale/i18n.web.ts b/src/locale/i18n.web.ts index d8e51723f..334b2586e 100644 --- a/src/locale/i18n.web.ts +++ b/src/locale/i18n.web.ts @@ -20,6 +20,10 @@ export async function dynamicActivate(locale: AppLanguage) { mod = await import(`./locales/es/messages`) break } + case AppLanguage.fi: { + mod = await import(`./locales/fi/messages`) + break + } case AppLanguage.fr: { mod = await import(`./locales/fr/messages`) break diff --git a/src/locale/languages.ts b/src/locale/languages.ts index 3fdabd02e..1cbe8fa83 100644 --- a/src/locale/languages.ts +++ b/src/locale/languages.ts @@ -8,6 +8,7 @@ export enum AppLanguage { en = 'en', de = 'de', es = 'es', + fi = 'fi', fr = 'fr', hi = 'hi', id = 'id', @@ -29,6 +30,7 @@ export const APP_LANGUAGES: AppLanguageConfig[] = [ {code2: AppLanguage.en, name: 'English'}, {code2: AppLanguage.de, name: 'Deutsch – German'}, {code2: AppLanguage.es, name: 'Español – Spanish'}, + {code2: AppLanguage.fi, name: 'Suomi – Finnish'}, {code2: AppLanguage.fr, name: 'Français – French'}, {code2: AppLanguage.hi, name: 'हिंदी – Hindi'}, {code2: AppLanguage.id, name: 'Bahasa Indonesia – Indonesian'}, diff --git a/src/locale/locales/ca/messages.po b/src/locale/locales/ca/messages.po index b52bb7613..0fd11dab6 100644 --- a/src/locale/locales/ca/messages.po +++ b/src/locale/locales/ca/messages.po @@ -244,12 +244,12 @@ msgstr "Avançat" #: src/view/screens/Feeds.tsx:666 msgid "All the feeds you've saved, right in one place." -msgstr "" +msgstr "Tots els canals que has desat, en un sol lloc." #: src/view/com/auth/login/ForgotPasswordForm.tsx:221 #: src/view/com/modals/ChangePassword.tsx:168 msgid "Already have a code?" -msgstr "" +msgstr "Ja tens un codi?" #: src/view/com/auth/login/ChooseAccountForm.tsx:98 msgid "Already signed in as @{0}" @@ -287,7 +287,7 @@ msgstr "i" #: src/screens/Onboarding/index.tsx:32 msgid "Animals" -msgstr "" +msgstr "Animals" #: src/view/screens/LanguageSettings.tsx:95 msgid "App Language" @@ -366,7 +366,7 @@ msgstr "Està s escrivint en <0>{0}</0>?" #: src/screens/Onboarding/index.tsx:26 msgid "Art" -msgstr "" +msgstr "Art" #: src/view/com/modals/SelfLabel.tsx:123 msgid "Artistic or non-erotic nudity." @@ -393,7 +393,7 @@ msgstr "Endarrere" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:136 msgid "Based on your interest in {interestsText}" -msgstr "" +msgstr "Segons els teus interessos en {interestsText}" #: src/view/screens/Settings/index.tsx:523 msgid "Basics" @@ -472,7 +472,7 @@ msgstr "Bluesky" #: src/view/com/auth/server-input/index.tsx:150 msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers." -msgstr "" +msgstr "Bluesky és una xarxa oberta on pots escollir el teu proveïdor d'allotjament. L'allotjament personalitzat està disponible en beta per a desenvolupadors" #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:80 @@ -503,7 +503,7 @@ msgstr "Bluesky no mostrarà el teu perfil ni les publicacions als usuaris que n #: src/screens/Onboarding/index.tsx:33 msgid "Books" -msgstr "" +msgstr "Llibres" #: src/view/screens/Settings/index.tsx:859 msgid "Build version {0} {1}" @@ -631,11 +631,11 @@ msgstr "Canvia el meu correu" #: src/view/screens/Settings/index.tsx:732 msgid "Change password" -msgstr "" +msgstr "Canvia la contrasenya" #: src/view/screens/Settings/index.tsx:741 msgid "Change Password" -msgstr "" +msgstr "Canvia la contrasenya" #: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 msgid "Change post language to {0}" @@ -643,7 +643,7 @@ msgstr "Canvia l'idioma de la publicació a {0}" #: src/view/screens/Settings/index.tsx:733 msgid "Change your Bluesky password" -msgstr "" +msgstr "Canvia la teva contrasenya de Bluesky" #: src/view/com/modals/ChangeEmail.tsx:109 msgid "Change Your Email" @@ -652,7 +652,7 @@ msgstr "Canvia el teu correu" #: src/screens/Deactivated.tsx:72 #: src/screens/Deactivated.tsx:76 msgid "Check my status" -msgstr "" +msgstr "Comprova el meu estat" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:121 msgid "Check out some recommended feeds. Tap + to add them to your list of pinned feeds." @@ -680,7 +680,7 @@ msgstr "Tria un servei" #: src/screens/Onboarding/StepFinished.tsx:135 msgid "Choose the algorithms that power your custom feeds." -msgstr "" +msgstr "Tria els algoritmes que alimentaran els teus canals personalitzats." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:83 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:83 @@ -689,7 +689,7 @@ msgstr "Tria els algoritmes que potenciaran la teva experiència amb els canals #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 msgid "Choose your main feeds" -msgstr "" +msgstr "Tria els teus canals principals" #: src/view/com/auth/create/Step1.tsx:196 msgid "Choose your password" @@ -732,12 +732,12 @@ msgstr "" #: src/screens/Onboarding/index.tsx:35 msgid "Climate" -msgstr "" +msgstr "Clima" #: src/view/com/modals/ChangePassword.tsx:265 #: src/view/com/modals/ChangePassword.tsx:268 msgid "Close" -msgstr "" +msgstr "Tanca" #: src/components/Dialog/index.web.tsx:84 #: src/components/Dialog/index.web.tsx:198 @@ -790,11 +790,11 @@ msgstr "Plega la llista d'usuaris per una notificació concreta" #: src/screens/Onboarding/index.tsx:41 msgid "Comedy" -msgstr "" +msgstr "Comèdia" #: src/screens/Onboarding/index.tsx:27 msgid "Comics" -msgstr "" +msgstr "Còmics" #: src/Navigation.tsx:229 #: src/view/screens/CommunityGuidelines.tsx:32 @@ -803,7 +803,7 @@ msgstr "Directrius de la comunitat" #: src/screens/Onboarding/StepFinished.tsx:148 msgid "Complete onboarding and start using your account" -msgstr "" +msgstr "Finalitza el registre i comença a utilitzar el teu compte" #: src/view/com/auth/create/Step3.tsx:73 msgid "Complete the challenge" @@ -819,7 +819,7 @@ msgstr "Redacta una resposta" #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:67 msgid "Configure content filtering setting for category: {0}" -msgstr "" +msgstr "Configura els filtres de continguts per la categoria: {0}" #: src/components/Prompt.tsx:124 #: src/view/com/modals/AppealLabel.tsx:98 @@ -914,19 +914,19 @@ msgstr "Continua" #: src/screens/Onboarding/StepModeration/index.tsx:115 #: src/screens/Onboarding/StepTopicalFeeds.tsx:111 msgid "Continue to next step" -msgstr "" +msgstr "Continua" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:167 msgid "Continue to the next step" -msgstr "" +msgstr "Continua" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:191 msgid "Continue to the next step without following any accounts" -msgstr "" +msgstr "Continua sense seguir cap compte" #: src/screens/Onboarding/index.tsx:44 msgid "Cooking" -msgstr "" +msgstr "Cuina" #: src/view/com/modals/AddAppPasswords.tsx:195 #: src/view/com/modals/InviteCodes.tsx:182 @@ -1027,12 +1027,12 @@ msgstr "Crea una targeta amb una minuatura. La targeta enllaça a {url}" #: src/screens/Onboarding/index.tsx:29 msgid "Culture" -msgstr "" +msgstr "Cultura" #: src/view/com/auth/server-input/index.tsx:95 #: src/view/com/auth/server-input/index.tsx:96 msgid "Custom" -msgstr "" +msgstr "Personalitzat" #: src/view/com/modals/ChangeHandle.tsx:389 msgid "Custom domain" @@ -1041,7 +1041,7 @@ msgstr "Domini personalitzat" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 #: src/view/screens/Feeds.tsx:692 msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." -msgstr "" +msgstr "Els canals personalitzats fets per la comunitat et porten noves experiències i t'ajuden a trobar contingut que t'agradarà ." #: src/view/screens/PreferencesExternalEmbeds.tsx:55 msgid "Customize media from external sites." @@ -1062,7 +1062,7 @@ msgstr "Mode fosc" #: src/view/screens/Settings/index.tsx:498 msgid "Dark Theme" -msgstr "" +msgstr "Tema fosc" #: src/view/screens/Debug.tsx:83 msgid "Debug panel" @@ -1096,7 +1096,7 @@ msgstr "Elimina el meu compte" #: src/view/screens/Settings/index.tsx:784 msgid "Delete My Account…" -msgstr "" +msgstr "Elimina el meu compte…" #: src/view/com/util/forms/PostDropdownBtn.tsx:317 #: src/view/com/util/forms/PostDropdownBtn.tsx:326 @@ -1136,7 +1136,7 @@ msgstr "Vols dir alguna cosa?" #: src/view/screens/Settings/index.tsx:504 msgid "Dim" -msgstr "" +msgstr "Tènue" #: src/view/com/composer/Composer.tsx:151 msgid "Discard" @@ -1161,7 +1161,7 @@ msgstr "Descobreix nous canals personalitzats" #: src/view/screens/Feeds.tsx:689 msgid "Discover New Feeds" -msgstr "" +msgstr "Descobreix nous canals" #: src/view/com/modals/EditProfile.tsx:192 msgid "Display name" @@ -1218,20 +1218,20 @@ msgstr "Fes doble toc per iniciar la sessió" #: src/view/screens/Settings/index.tsx:755 msgid "Download Bluesky account data (repository)" -msgstr "" +msgstr "Descarrega les dades del compte de Bluesky (repositori)" #: src/view/screens/Settings/ExportCarDialog.tsx:59 #: src/view/screens/Settings/ExportCarDialog.tsx:63 msgid "Download CAR file" -msgstr "" +msgstr "Descarrega el fitxer CAR" #: src/view/com/composer/text-input/TextInput.web.tsx:249 msgid "Drop to add images" -msgstr "" +msgstr "Deixa anar per afegir imatges" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:111 msgid "Due to Apple policies, adult content can only be enabled on the web after completing sign up." -msgstr "" +msgstr "Degut a les polÃtiques d'Apple, el contingut per a adults només es pot habilitar a la web després de registrar-se" #: src/view/com/modals/EditProfile.tsx:185 msgid "e.g. Alice Roberts" @@ -1316,7 +1316,7 @@ msgstr "Edita la descripció del teu perfil" #: src/screens/Onboarding/index.tsx:34 msgid "Education" -msgstr "" +msgstr "Ensenyament" #: src/view/com/auth/create/Step1.tsx:176 #: src/view/com/auth/login/ForgotPasswordForm.tsx:156 @@ -1357,7 +1357,7 @@ msgstr "Habilita el contingut per a adults" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:76 #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:77 msgid "Enable adult content in your feeds" -msgstr "" +msgstr "Habilita veure el contingut per adults als teus canals" #: src/view/com/modals/EmbedConsent.tsx:97 msgid "Enable External Media" @@ -1394,7 +1394,7 @@ msgstr "Entra el codi de confirmació" #: src/view/com/modals/ChangePassword.tsx:151 msgid "Enter the code you received to change your password." -msgstr "" +msgstr "Introdueix el codi que has rebut per canviar la teva contrasenya." #: src/view/com/modals/ChangeHandle.tsx:371 msgid "Enter the domain you want to use" @@ -1473,12 +1473,12 @@ msgstr "Expandeix o replega la publicació completa a la qual està s responent" #: src/view/screens/Settings/index.tsx:753 msgid "Export my data" -msgstr "" +msgstr "Exporta les meves dades" #: src/view/screens/Settings/ExportCarDialog.tsx:44 #: src/view/screens/Settings/index.tsx:764 msgid "Export My Data" -msgstr "" +msgstr "Exporta les meves dades" #: src/view/com/modals/EmbedConsent.tsx:64 msgid "External Media" @@ -1559,11 +1559,11 @@ msgstr "Els canals són algoritmes personalitzats creats per usuaris que coneixe #: src/screens/Onboarding/StepTopicalFeeds.tsx:76 msgid "Feeds can be topical as well!" -msgstr "" +msgstr "Els canals també poden ser d'actualitat!" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Finalizing" -msgstr "" +msgstr "Finalitzant" #: src/view/com/posts/CustomFeedEmptyState.tsx:47 #: src/view/com/posts/FollowingEmptyState.tsx:57 @@ -1597,11 +1597,11 @@ msgstr "Ajusta els fils de debat." #: src/screens/Onboarding/index.tsx:38 msgid "Fitness" -msgstr "" +msgstr "Exercici" #: src/screens/Onboarding/StepFinished.tsx:131 msgid "Flexible" -msgstr "" +msgstr "Flexible" #: src/view/com/modals/EditImage.tsx:115 msgid "Flip horizontal" @@ -1631,11 +1631,11 @@ msgstr "Segueix {0}" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:179 msgid "Follow All" -msgstr "" +msgstr "Segueix-los a tots" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:174 msgid "Follow selected accounts and continue to the next step" -msgstr "" +msgstr "Segueix els comptes seleccionats i continua" #: src/view/com/auth/onboarding/RecommendedFollows.tsx:64 msgid "Follow some users to get started. We can recommend you more users based on who you find interesting." @@ -1693,7 +1693,7 @@ msgstr "Et segueix" #: src/screens/Onboarding/index.tsx:43 msgid "Food" -msgstr "" +msgstr "Menjar" #: src/view/com/modals/DeleteAccount.tsx:111 msgid "For security reasons, we'll need to send a confirmation code to your email address." @@ -1752,7 +1752,7 @@ msgstr "Ves enrere" #: src/screens/Onboarding/Layout.tsx:104 #: src/screens/Onboarding/Layout.tsx:193 msgid "Go back to previous step" -msgstr "" +msgstr "Ves al pas anterior" #: src/view/screens/Search/Search.tsx:747 #: src/view/shell/desktop/Search.tsx:262 @@ -1794,15 +1794,15 @@ msgstr "Ajuda" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:132 msgid "Here are some accounts for you to follow" -msgstr "" +msgstr "Aquà tens uns quants comptes que pots seguir" #: src/screens/Onboarding/StepTopicalFeeds.tsx:85 msgid "Here are some popular topical feeds. You can choose to follow as many as you like." -msgstr "" +msgstr "Aquà tens alguns canals d'actualitat populars. Pots seguir-ne tants com vulguis." #: src/screens/Onboarding/StepTopicalFeeds.tsx:80 msgid "Here are some topical feeds based on your interests: {interestsText}. You can choose to follow as many as you like." -msgstr "" +msgstr "Aquà tens uns quants canals d'actualitat basats en els teus interesos: {interestsText}. Pots seguir-ne tants com vulguis." #: src/view/com/modals/AddAppPasswords.tsx:153 msgid "Here is your app password." @@ -1914,7 +1914,7 @@ msgstr "Si no en selecciones cap, és apropiat per a totes les edats." #: src/view/com/modals/ChangePassword.tsx:146 msgid "If you want to change your password, we will send you a code to verify that this is your account." -msgstr "" +msgstr "Si vols canviar la contrasenya t'enviarem un codi per verificar que aquest compte és teu." #: src/view/com/util/images/Gallery.tsx:38 msgid "Image" @@ -2024,7 +2024,7 @@ msgstr "Codis d'invitació: 1 disponible" #: src/screens/Onboarding/StepFollowingFeed.tsx:64 msgid "It shows posts from the people you follow as they happen." -msgstr "" +msgstr "Mostra les publicacions de les persones que segueixes cronològicament." #: src/view/com/auth/HomeLoggedOutCTA.tsx:99 #: src/view/com/auth/SplashScreen.web.tsx:138 @@ -2046,7 +2046,7 @@ msgstr "Feines" #: src/screens/Onboarding/index.tsx:24 msgid "Journalism" -msgstr "" +msgstr "Periodisme" #: src/view/com/composer/select-language/SelectLangBtn.tsx:104 msgid "Language selection" @@ -2101,7 +2101,7 @@ msgstr "Sortint de Bluesky" #: src/screens/Deactivated.tsx:128 msgid "left to go." -msgstr "" +msgstr "queda." #: src/view/screens/Settings/index.tsx:278 msgid "Legacy storage cleared, you need to restart the app now." @@ -2114,7 +2114,7 @@ msgstr "Restablirem la teva contrasenya!" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Let's go!" -msgstr "" +msgstr "Som-hi!" #: src/view/com/util/UserAvatar.tsx:248 #: src/view/com/util/UserBanner.tsx:62 @@ -2140,7 +2140,7 @@ msgstr "Li ha agradat a" #: src/view/screens/PostLikedBy.tsx:27 #: src/view/screens/ProfileFeedLikedBy.tsx:27 msgid "Liked By" -msgstr "" +msgstr "Li ha agradat a" #: src/view/com/feeds/FeedSourceCard.tsx:279 msgid "Liked by {0} {1}" @@ -2152,7 +2152,7 @@ msgstr "Li ha agradat a {likeCount} {0}" #: src/view/com/notifications/FeedItem.tsx:170 msgid "liked your custom feed" -msgstr "" +msgstr "els hi ha agradat el teu canal personalitzat" #: src/view/com/notifications/FeedItem.tsx:171 #~ msgid "liked your custom feed{0}" @@ -2247,7 +2247,7 @@ msgstr "Registre" #: src/screens/Deactivated.tsx:178 #: src/screens/Deactivated.tsx:181 msgid "Log out" -msgstr "" +msgstr "Desconnecta" #: src/view/screens/Moderation.tsx:155 msgid "Logged-out visibility" @@ -2477,7 +2477,7 @@ msgstr "Els meus canals desats" #: src/view/com/auth/server-input/index.tsx:118 msgid "my-server.com" -msgstr "" +msgstr "el-meu-servidor.com" #: src/view/com/modals/AddAppPasswords.tsx:179 #: src/view/com/modals/CreateOrEditList.tsx:290 @@ -2490,7 +2490,7 @@ msgstr "Es requereix un nom" #: src/screens/Onboarding/index.tsx:25 msgid "Nature" -msgstr "" +msgstr "Natura" #: src/view/com/auth/login/ForgotPasswordForm.tsx:190 #: src/view/com/auth/login/ForgotPasswordForm.tsx:219 @@ -2516,7 +2516,7 @@ msgstr "No perdis mai accés als teus seguidors ni a les teves dades." #: src/screens/Onboarding/StepFinished.tsx:119 msgid "Never lose access to your followers or data." -msgstr "" +msgstr "No perdis mai accés als teus seguidors i les teves dades." #: src/components/dialogs/MutedWords.tsx:293 msgid "Nevermind" @@ -2541,7 +2541,7 @@ msgstr "Nova contrasenya" #: src/view/com/modals/ChangePassword.tsx:215 msgid "New Password" -msgstr "" +msgstr "Nova contrasenya" #: src/view/com/feeds/FeedPage.tsx:126 msgctxt "action" @@ -2577,7 +2577,7 @@ msgstr "Les respostes més noves primer" #: src/screens/Onboarding/index.tsx:23 msgid "News" -msgstr "" +msgstr "NotÃcies" #: src/view/com/auth/create/CreateAccount.tsx:172 #: src/view/com/auth/login/ForgotPasswordForm.tsx:182 @@ -2687,7 +2687,7 @@ msgstr "Ostres!" #: src/screens/Onboarding/StepInterests/index.tsx:128 msgid "Oh no! Something went wrong." -msgstr "" +msgstr "Ostres! Alguna cosa ha fallat." #: src/view/com/auth/login/PasswordUpdatedForm.tsx:41 msgid "Okay" @@ -2721,7 +2721,7 @@ msgstr "Ostres!" #: src/screens/Onboarding/StepFinished.tsx:115 msgid "Open" -msgstr "" +msgstr "Obre" #: src/view/screens/Moderation.tsx:75 msgid "Open content filtering settings" @@ -2750,7 +2750,7 @@ msgstr "" #: src/view/screens/Settings/index.tsx:804 msgid "Open storybook page" -msgstr "" +msgstr "Obre la pà gina d'historial" #: src/view/com/util/forms/DropdownButton.tsx:154 msgid "Opens {numItems} options" @@ -2876,7 +2876,7 @@ msgstr "Pà gina no trobada" #: src/view/screens/NotFound.tsx:42 msgid "Page Not Found" -msgstr "" +msgstr "Pà gina no trobada" #: src/view/com/auth/create/Step1.tsx:191 #: src/view/com/auth/create/Step1.tsx:201 @@ -2912,7 +2912,7 @@ msgstr "S'ha denegat el permÃs per accedir a la cà mera. Activa'l a la configur #: src/screens/Onboarding/index.tsx:31 msgid "Pets" -msgstr "" +msgstr "Mascotes" #: src/view/com/auth/create/Step2.tsx:183 #~ msgid "Phone number" @@ -3010,7 +3010,7 @@ msgstr "Espera que es generi la targeta de l'enllaç" #: src/screens/Onboarding/index.tsx:37 msgid "Politics" -msgstr "" +msgstr "PolÃtica" #: src/view/com/modals/SelfLabel.tsx:111 msgid "Porn" @@ -3129,7 +3129,7 @@ msgstr "Protegeix el teu compte verificant el teu correu." #: src/screens/Onboarding/StepFinished.tsx:101 msgid "Public" -msgstr "" +msgstr "Públic" #: src/view/screens/ModerationModlists.tsx:61 msgid "Public, shareable lists of users to mute or block in bulk." @@ -3300,7 +3300,7 @@ msgstr "Informa de la publicació" #: src/view/com/util/post-ctrls/RepostButton.tsx:61 msgctxt "action" msgid "Repost" -msgstr "Respon" +msgstr "Republica" #: src/view/com/util/post-ctrls/RepostButton.web.tsx:48 msgid "Repost" @@ -3317,11 +3317,11 @@ msgstr "Republica o cita la publicació" #: src/view/screens/PostRepostedBy.tsx:27 msgid "Reposted By" -msgstr "" +msgstr "Republicat per" #: src/view/com/posts/FeedItem.tsx:207 msgid "Reposted by {0}" -msgstr "" +msgstr "Republicat per {0}" #: src/view/com/posts/FeedItem.tsx:206 #~ msgid "Reposted by {0})" @@ -3351,7 +3351,7 @@ msgstr "Demana un canvi" #: src/view/com/modals/ChangePassword.tsx:239 #: src/view/com/modals/ChangePassword.tsx:241 msgid "Request Code" -msgstr "" +msgstr "Demana un codi" #: src/view/screens/Settings/index.tsx:456 msgid "Require alt text before posting" @@ -3368,7 +3368,7 @@ msgstr "Codi de restabliment" #: src/view/com/modals/ChangePassword.tsx:190 msgid "Reset Code" -msgstr "" +msgstr "Codi de restabliment" #: src/view/screens/Settings/index.tsx:824 msgid "Reset onboarding" @@ -3475,7 +3475,7 @@ msgstr "Desa el canvi d'identificador a {handle}" #: src/screens/Onboarding/index.tsx:36 msgid "Science" -msgstr "" +msgstr "Ciència" #: src/view/screens/ProfileList.tsx:859 msgid "Scroll to top" @@ -3584,19 +3584,19 @@ msgstr "Selecciona el servei" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:52 msgid "Select some accounts below to follow" -msgstr "" +msgstr "Selecciona alguns d'aquests comptes per seguir-los" #: src/view/com/auth/server-input/index.tsx:82 msgid "Select the service that hosts your data." -msgstr "" +msgstr "Selecciona el servei que allotja les teves dades." #: src/screens/Onboarding/StepTopicalFeeds.tsx:96 msgid "Select topical feeds to follow from the list below" -msgstr "" +msgstr "Selecciona els canals d'actualitat per seguir d'aquesta llista" #: src/screens/Onboarding/StepModeration/index.tsx:75 msgid "Select what you want to see (or not see), and we’ll handle the rest." -msgstr "" +msgstr "Selecciona què vols veure (o què no vols veure) i nosaltres farem la resta." #: src/view/screens/LanguageSettings.tsx:281 msgid "Select which languages you want your subscribed feeds to include. If none are selected, all languages will be shown." @@ -3608,7 +3608,7 @@ msgstr "Selecciona l'idioma de l'aplicació perquè el text predeterminat es mos #: src/screens/Onboarding/StepInterests/index.tsx:196 msgid "Select your interests from the options below" -msgstr "" +msgstr "Selecciona els teus interesos d'entre aquestes opcions" #: src/view/com/auth/create/Step2.tsx:155 #~ msgid "Select your phone's country" @@ -3620,11 +3620,11 @@ msgstr "Selecciona el teu idioma preferit per a les traduccions al teu canal." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:116 msgid "Select your primary algorithmic feeds" -msgstr "" +msgstr "Selecciona els teus canals algorÃtmics primaris" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:142 msgid "Select your secondary algorithmic feeds" -msgstr "" +msgstr "Selecciona els teus canals algorÃtmics secundaris" #: src/view/com/modals/VerifyEmail.tsx:202 #: src/view/com/modals/VerifyEmail.tsx:204 @@ -3659,7 +3659,7 @@ msgstr "Envia un correu amb el codi de confirmació per l'eliminació del compte #: src/view/com/auth/server-input/index.tsx:110 msgid "Server address" -msgstr "" +msgstr "Adreça del servidor" #: src/view/com/modals/ContentFilteringSettings.tsx:311 msgid "Set {value} for {labelGroup} content moderation policy" @@ -3685,11 +3685,11 @@ msgstr "Estableix el tema de colors a la configuració del sistema" #: src/view/screens/Settings/index.tsx:514 msgid "Set dark theme to the dark theme" -msgstr "" +msgstr "Posa el tema fosc" #: src/view/screens/Settings/index.tsx:507 msgid "Set dark theme to the dim theme" -msgstr "" +msgstr "Posa el tema fosc al tema atenuat" #: src/view/com/auth/login/SetNewPasswordForm.tsx:104 msgid "Set new password" @@ -3725,7 +3725,7 @@ msgstr "" #: src/screens/Onboarding/Layout.tsx:50 msgid "Set up your account" -msgstr "" +msgstr "Configura el teu compte" #: src/view/com/modals/ChangeHandle.tsx:266 msgid "Sets Bluesky username" @@ -3813,15 +3813,15 @@ msgstr "Mostra les publicacions citades" #: src/screens/Onboarding/StepFollowingFeed.tsx:118 msgid "Show quote-posts in Following feed" -msgstr "" +msgstr "Mostra les publicacions citades en el canal Seguint" #: src/screens/Onboarding/StepFollowingFeed.tsx:134 msgid "Show quotes in Following" -msgstr "" +msgstr "Mostra els citats a Seguint" #: src/screens/Onboarding/StepFollowingFeed.tsx:94 msgid "Show re-posts in Following feed" -msgstr "" +msgstr "Mostra les republicacions al canal Seguint" #: src/view/screens/PreferencesFollowingFeed.tsx:119 msgid "Show Replies" @@ -3833,11 +3833,11 @@ msgstr "Mostra les respostes dels comptes que segueixes abans que les altres." #: src/screens/Onboarding/StepFollowingFeed.tsx:86 msgid "Show replies in Following" -msgstr "" +msgstr "Mostra les respostes a Seguint" #: src/screens/Onboarding/StepFollowingFeed.tsx:70 msgid "Show replies in Following feed" -msgstr "" +msgstr "Mostrea les respostes al canal Seguint" #: src/view/screens/PreferencesFollowingFeed.tsx:70 msgid "Show replies with at least {value} {0}" @@ -3849,7 +3849,7 @@ msgstr "Mostra republicacions" #: src/screens/Onboarding/StepFollowingFeed.tsx:110 msgid "Show reposts in Following" -msgstr "" +msgstr "Mostra les republicacions al canal Seguint" #: src/view/com/util/moderation/ContentHider.tsx:67 #: src/view/com/util/moderation/PostHider.tsx:61 @@ -3949,7 +3949,7 @@ msgstr "Salta aquest pas" #: src/screens/Onboarding/StepInterests/index.tsx:232 msgid "Skip this flow" -msgstr "" +msgstr "Salta aquest flux" #: src/view/com/auth/create/Step2.tsx:82 #~ msgid "SMS verification" @@ -3957,7 +3957,7 @@ msgstr "" #: src/screens/Onboarding/index.tsx:40 msgid "Software Dev" -msgstr "" +msgstr "Desenvolupament de programari" #: src/view/com/modals/ProfilePreview.tsx:62 #~ msgid "Something went wrong and we're not sure what." @@ -3985,7 +3985,7 @@ msgstr "Ordena les respostes a la mateixa publicació per:" #: src/screens/Onboarding/index.tsx:30 msgid "Sports" -msgstr "" +msgstr "Esports" #: src/view/com/modals/crop-image/CropImage.web.tsx:122 msgid "Square" @@ -4023,7 +4023,7 @@ msgstr "Subscriure's" #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:173 #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:308 msgid "Subscribe to the {0} feed" -msgstr "" +msgstr "Subscriu-te al canal {0}" #: src/view/screens/ProfileList.tsx:604 msgid "Subscribe to this list" @@ -4095,7 +4095,7 @@ msgstr "Toca per veure-ho completament" #: src/screens/Onboarding/index.tsx:39 msgid "Tech" -msgstr "" +msgstr "Tecnologia" #: src/view/shell/desktop/RightNav.tsx:81 msgid "Terms" @@ -4135,7 +4135,7 @@ msgstr "La polÃtica de drets d'autoria ha estat traslladada a <0/>" #: src/screens/Onboarding/Layout.tsx:60 msgid "The following steps will help customize your Bluesky experience." -msgstr "" +msgstr "Els següents passos t'ajudaran a personalitzar la teva experiència a Bluesky." #: src/view/com/post-thread/PostThread.tsx:517 msgid "The post may have been deleted." @@ -4159,7 +4159,7 @@ msgstr "Les condicions del servei han estat traslladades a " #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:150 msgid "There are many feeds to try:" -msgstr "" +msgstr "Hi ha molts canals per provar:" #: src/view/screens/ProfileFeed.tsx:550 msgid "There was an an issue contacting the server, please check your internet connection and try again." @@ -4239,7 +4239,7 @@ msgstr "S'ha produït un problema inesperat a l'aplicació. Fes-nos saber si aix #: src/screens/Deactivated.tsx:106 msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." -msgstr "" +msgstr "Hi ha hagut una gran quantitat d'usuaris nous a Bluesky! Activarem el teu compte tan aviat com puguem." #: src/view/com/auth/create/Step2.tsx:55 #~ msgid "There's something wrong with this number. Please choose your country and enter your full phone number!" @@ -4247,7 +4247,7 @@ msgstr "" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:138 msgid "These are popular accounts you might like:" -msgstr "" +msgstr "Aquests són alguns comptes populars que et poden agradar:" #~ msgid "This {0} has been labeled." #~ msgstr "Aquest {0} ha estat etiquetat." @@ -4274,7 +4274,7 @@ msgstr "Aquest contingut no es pot veure sense un compte de Bluesky." #: src/view/screens/Settings/ExportCarDialog.tsx:75 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost.</0>" -msgstr "" +msgstr "Aquesta funcionalitat està en beta. En <0>aquesta entrada al blog</0> tens més informació." #: src/view/com/posts/FeedErrorMessage.tsx:114 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." @@ -4328,7 +4328,7 @@ msgstr "Aquest usuari està inclós a la llista <0/> que tens bloquejada" #: src/view/com/modals/ModerationDetails.tsx:74 msgid "This user is included in the <0/> list which you have muted." -msgstr "" +msgstr "Aquest usuari està inclòs a la llista <0/> que has silenciat." #: src/view/com/modals/ModerationDetails.tsx:74 #~ msgid "This user is included the <0/> list which you have muted." @@ -4614,7 +4614,7 @@ msgstr "Verifica el teu correu" #: src/screens/Onboarding/index.tsx:42 msgid "Video Games" -msgstr "" +msgstr "Videojocs" #: src/view/com/profile/ProfileHeader.tsx:662 msgid "View {0}'s avatar" @@ -4647,7 +4647,7 @@ msgstr "Adverteix" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:134 msgid "We also think you'll like \"For You\" by Skygaze:" -msgstr "" +msgstr "També creiem que t'agradarà el canal \"For You\" d'Skygaze:" #: src/screens/Hashtag.tsx:132 msgid "We couldn't find any results for that hashtag." @@ -4655,11 +4655,11 @@ msgstr "" #: src/screens/Deactivated.tsx:133 msgid "We estimate {estimatedTime} until your account is ready." -msgstr "" +msgstr "Calculem {estimatedTime} fins que el teu compte estigui llest." #: src/screens/Onboarding/StepFinished.tsx:93 msgid "We hope you have a wonderful time. Remember, Bluesky is:" -msgstr "" +msgstr "Esperem que t'ho passis pipa. Recorda que Bluesky és:" #: src/view/com/posts/DiscoverFallbackHeader.tsx:29 msgid "We ran out of posts from your follows. Here's the latest from <0/>." @@ -4671,15 +4671,15 @@ msgstr "" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124 msgid "We recommend our \"Discover\" feed:" -msgstr "" +msgstr "Et reomanem el nostre canal \"Discover\":" #: src/screens/Onboarding/StepInterests/index.tsx:133 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 "" +msgstr "No ens hem pogut connectar. Torna-ho a provar per continuar configurant el teu compte. Si continua fallant, pots ometre aquest flux." #: src/screens/Deactivated.tsx:137 msgid "We will let you know when your account is ready." -msgstr "" +msgstr "T'informarem quan el teu compte estigui llest." #: src/view/com/modals/AppealLabel.tsx:48 msgid "We'll look into your appeal promptly." @@ -4687,7 +4687,7 @@ msgstr "Analitzarem la teva apel·lació rà pidament." #: src/screens/Onboarding/StepInterests/index.tsx:138 msgid "We'll use this to help customize your experience." -msgstr "" +msgstr "Ho farem servir per personalitzar la teva experiència." #: src/view/com/auth/create/CreateAccount.tsx:134 msgid "We're so excited to have you join us!" @@ -4716,7 +4716,7 @@ msgstr "Benvingut a <0>Bluesky</0>" #: src/screens/Onboarding/StepInterests/index.tsx:130 msgid "What are your interests?" -msgstr "" +msgstr "Quins són els teus interesos?" #: src/view/com/modals/report/Modal.tsx:169 msgid "What is the issue with this {collectionName}?" @@ -4758,7 +4758,7 @@ msgstr "Escriu la teva resposta" #: src/screens/Onboarding/index.tsx:28 msgid "Writers" -msgstr "" +msgstr "Escriptors" #: src/view/com/auth/create/Step2.tsx:263 #~ msgid "XXXXXX" @@ -4776,7 +4776,7 @@ msgstr "SÃ" #: src/screens/Deactivated.tsx:130 msgid "You are in line." -msgstr "" +msgstr "Està s a la cua." #: src/view/com/posts/FollowingEmptyState.tsx:67 #: src/view/com/posts/FollowingEndOfFeed.tsx:68 @@ -4789,7 +4789,7 @@ msgstr "També pots descobrir nous canals personalitzats per seguir." #: src/screens/Onboarding/StepFollowingFeed.tsx:142 msgid "You can change these settings later." -msgstr "" +msgstr "Pots canviar aquests parà metres més endavant." #: src/view/com/auth/login/Login.tsx:158 #: src/view/com/auth/login/PasswordUpdatedForm.tsx:31 @@ -4825,7 +4825,7 @@ msgstr "Has bloquejat aquest usuari. No pots veure el seu contingut." #: src/view/com/modals/ChangePassword.tsx:87 #: src/view/com/modals/ChangePassword.tsx:121 msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." -msgstr "" +msgstr "Has entrat un codi invà lid. Hauria de ser tipus XXXXX-XXXXX." #: src/view/com/modals/ModerationDetails.tsx:87 msgid "You have muted this user." @@ -4862,7 +4862,7 @@ msgstr "Has de tenir 18 anys o més per habilitar el contingut per a adults." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:103 msgid "You must be 18 years or older to enable adult content" -msgstr "" +msgstr "Has de tenir 18 anys o més per habilitar el contingut per a adults" #: src/view/com/util/forms/PostDropdownBtn.tsx:147 msgid "You will no longer receive notifications for this thread" @@ -4878,17 +4878,17 @@ msgstr "Rebrà s un correu amb un \"codi de restabliment\". Introdueix aquà el c #: src/screens/Onboarding/StepModeration/index.tsx:72 msgid "You're in control" -msgstr "" +msgstr "Tu tens el control" #: src/screens/Deactivated.tsx:87 #: src/screens/Deactivated.tsx:88 #: src/screens/Deactivated.tsx:103 msgid "You're in line" -msgstr "" +msgstr "Està s a la cua" #: src/screens/Onboarding/StepFinished.tsx:90 msgid "You're ready to go!" -msgstr "" +msgstr "Ja està tot llest!" #: src/view/com/posts/FollowingEndOfFeed.tsx:48 msgid "You've reached the end of your feed! Find some more accounts to follow." @@ -4904,7 +4904,7 @@ msgstr "El teu compte s'ha eliminat" #: src/view/screens/Settings/ExportCarDialog.tsx:47 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 "" +msgstr "El repositori del teu compte, que conté tots els registres de dades públiques, es pot baixar com a fitxer \"CAR\". Aquest fitxer no inclou incrustacions multimèdia, com ara imatges, ni les teves dades privades, que s'han d'obtenir per separat." #: src/view/com/auth/create/Step1.tsx:215 msgid "Your birth date" @@ -4916,7 +4916,7 @@ msgstr "La teva elecció es desarà , però es pot canviar més endavant a la con #: src/screens/Onboarding/StepFollowingFeed.tsx:61 msgid "Your default feed is \"Following\"" -msgstr "" +msgstr "El teu canal per defecte és \"Seguint\"" #: src/view/com/auth/create/state.ts:110 #: src/view/com/auth/login/ForgotPasswordForm.tsx:70 @@ -4964,7 +4964,7 @@ msgstr "" #: src/view/com/modals/ChangePassword.tsx:155 msgid "Your password has been changed successfully!" -msgstr "" +msgstr "S'ha canviat la teva contrasenya!" #: src/view/com/composer/Composer.tsx:274 msgid "Your post has been published" diff --git a/src/locale/locales/de/messages.po b/src/locale/locales/de/messages.po index 5182cf581..503d656ee 100644 --- a/src/locale/locales/de/messages.po +++ b/src/locale/locales/de/messages.po @@ -8,40 +8,22 @@ msgstr "" "Language: de\n" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: \n" +"PO-Revision-Date: 2024-03-12 13:00+0000\n" "Last-Translator: \n" -"Language-Team: \n" +"Language-Team: Translators in PR 2319, PythooonUser, cdfzo\n" "Plural-Forms: \n" #: src/view/com/modals/VerifyEmail.tsx:142 msgid "(no email)" -msgstr "" - -#: src/view/shell/desktop/RightNav.tsx:168 -#~ msgid "{0, plural, one {# invite code available} other {# invite codes available}}" -#~ msgstr "" +msgstr "(keine E-Mail)" #: src/view/com/profile/ProfileHeader.tsx:593 msgid "{following} following" -msgstr "" - -#: src/view/shell/desktop/RightNav.tsx:151 -#~ msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" -#~ msgstr "" - -#: src/view/screens/Settings.tsx:435 -#: src/view/shell/Drawer.tsx:664 -#~ msgid "{invitesAvailable} invite code available" -#~ msgstr "{invitesAvailable} Einladungscode verfügbar" - -#: src/view/screens/Settings.tsx:437 -#: src/view/shell/Drawer.tsx:666 -#~ msgid "{invitesAvailable} invite codes available" -#~ msgstr "{invitesAvailable} Einladungscodes verfügbar" +msgstr "{following} folge ich" #: src/view/shell/Drawer.tsx:440 msgid "{numUnreadNotifications} unread" -msgstr "" +msgstr "{numUnreadNotifications} ungelesen" #: src/view/com/threadgate/WhoCanReply.tsx:158 msgid "<0/> members" @@ -49,7 +31,7 @@ msgstr "<0/> Mitglieder" #: src/view/com/profile/ProfileHeader.tsx:595 msgid "<0>{following} </0><1>following</1>" -msgstr "" +msgstr "<0>{following} </0><1>folge ich</1>" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:30 msgid "<0>Choose your</0><1>Recommended</1><2>Feeds</2>" @@ -61,11 +43,11 @@ msgstr "<0>Folge einigen</0><1>empfohlenen</1><2>Nutzern</2>" #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:21 msgid "<0>Welcome to</0><1>Bluesky</1>" -msgstr "" +msgstr "<0>Willkommen bei</0><1>Bluesky</1>" #: src/view/com/profile/ProfileHeader.tsx:558 msgid "âš Invalid Handle" -msgstr "" +msgstr "âš Ungültiger Handle" #: src/view/com/util/moderation/LabelInfo.tsx:45 msgid "A content warning has been applied to this {0}." @@ -78,11 +60,11 @@ msgstr "Eine neue Version der App ist verfügbar. Bitte aktualisiere die App, um #: src/view/com/util/ViewHeader.tsx:89 #: src/view/screens/Search/Search.tsx:647 msgid "Access navigation links and settings" -msgstr "" +msgstr "Zugriff auf Navigationslinks und Einstellungen" #: src/view/com/home/HomeHeaderLayoutMobile.tsx:52 msgid "Access profile and other navigation links" -msgstr "" +msgstr "Zugang zum Profil und anderen Navigationslinks" #: src/view/com/modals/EditImage.tsx:299 #: src/view/screens/Settings/index.tsx:451 @@ -97,19 +79,19 @@ msgstr "Konto" #: src/view/com/profile/ProfileHeader.tsx:246 msgid "Account blocked" -msgstr "" +msgstr "Konto blockiert" #: src/view/com/profile/ProfileHeader.tsx:213 msgid "Account muted" -msgstr "" +msgstr "Konto stummgeschaltet" #: src/view/com/modals/ModerationDetails.tsx:86 msgid "Account Muted" -msgstr "" +msgstr "Konto Stummgeschaltet" #: src/view/com/modals/ModerationDetails.tsx:72 msgid "Account Muted by List" -msgstr "" +msgstr "Konto stummgeschaltet nach Liste" #: src/view/com/util/AccountDropdownBtn.tsx:41 msgid "Account options" @@ -117,15 +99,15 @@ msgstr "Kontoeinstellungen" #: src/view/com/util/AccountDropdownBtn.tsx:25 msgid "Account removed from quick access" -msgstr "" +msgstr "Konto aus dem Schnellzugriff entfernt" #: src/view/com/profile/ProfileHeader.tsx:268 msgid "Account unblocked" -msgstr "" +msgstr "Konto entblockiert" #: src/view/com/profile/ProfileHeader.tsx:226 msgid "Account unmuted" -msgstr "" +msgstr "Konto Stummschaltung aufgehoben" #: src/components/dialogs/MutedWords.tsx:165 #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:150 @@ -158,7 +140,7 @@ msgstr "Alt-Text hinzufügen" #: src/view/screens/AppPasswords.tsx:143 #: src/view/screens/AppPasswords.tsx:156 msgid "Add App Password" -msgstr "" +msgstr "App-Passwort hinzufügen" #: src/view/com/modals/report/InputIssueDetails.tsx:41 #: src/view/com/modals/report/Modal.tsx:191 @@ -179,11 +161,11 @@ msgstr "Link-Karte hinzufügen:" #: src/components/dialogs/MutedWords.tsx:158 msgid "Add mute word for configured settings" -msgstr "" +msgstr "Stummgeschaltetes Wort für konfigurierte Einstellungen hinzufügen" #: src/components/dialogs/MutedWords.tsx:87 msgid "Add muted words and tags" -msgstr "" +msgstr "Füge stummgeschaltete Wörter und Tags hinzu" #: src/view/com/modals/ChangeHandle.tsx:417 msgid "Add the following DNS record to your domain:" @@ -200,7 +182,7 @@ msgstr "Zu meinen Feeds hinzufügen" #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:139 msgid "Added" -msgstr "" +msgstr "Hinzugefügt" #: src/view/com/modals/ListAddRemoveUsers.tsx:191 #: src/view/com/modals/UserAddRemoveLists.tsx:144 @@ -209,7 +191,7 @@ msgstr "Zur Liste hinzugefügt" #: src/view/com/feeds/FeedSourceCard.tsx:127 msgid "Added to my feeds" -msgstr "" +msgstr "Zu meinen Feeds hinzugefügt" #: src/view/screens/PreferencesFollowingFeed.tsx:173 msgid "Adjust the number of likes a reply must have to be shown in your feed." @@ -221,11 +203,7 @@ msgstr "Inhalt für Erwachsene" #: src/view/com/modals/ContentFilteringSettings.tsx:141 msgid "Adult content can only be enabled via the Web at <0/>." -msgstr "" - -#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:78 -#~ msgid "Adult content can only be enabled via the Web at <0>bsky.app</0>." -#~ msgstr "" +msgstr "Inhalte für Erwachsene können nur über das Web unter <0/> aktiviert werden." #: src/view/screens/Settings/index.tsx:664 msgid "Advanced" @@ -233,16 +211,16 @@ msgstr "Erweitert" #: src/view/screens/Feeds.tsx:666 msgid "All the feeds you've saved, right in one place." -msgstr "" +msgstr "All deine gespeicherten Feeds an einem Ort." #: src/view/com/auth/login/ForgotPasswordForm.tsx:221 #: src/view/com/modals/ChangePassword.tsx:168 msgid "Already have a code?" -msgstr "" +msgstr "Hast du bereits einen Code?" #: src/view/com/auth/login/ChooseAccountForm.tsx:98 msgid "Already signed in as @{0}" -msgstr "" +msgstr "Bereits angemeldet als @{0}" #: src/view/com/composer/photos/Gallery.tsx:130 msgid "ALT" @@ -258,7 +236,7 @@ msgstr "Alt-Text beschreibt Bilder für blinde und sehbehinderte Nutzer und hilf #: src/view/com/modals/VerifyEmail.tsx:124 msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below." -msgstr "Eine E-Mail wurde an {0} gesendet . Sie enthält einen Bestätigungscode, den du unten eingeben kannst." +msgstr "Eine E-Mail wurde an {0} gesendet. Sie enthält einen Bestätigungscode, den du unten eingeben kannst." #: src/view/com/modals/ChangeEmail.tsx:119 msgid "An email has been sent to your previous address, {0}. It includes a confirmation code which you can enter below." @@ -267,7 +245,7 @@ msgstr "Eine E-Mail wurde an deine vorherige Adresse {0} gesendet. Sie enthält #: src/view/com/profile/FollowButton.tsx:30 #: src/view/com/profile/FollowButton.tsx:40 msgid "An issue occurred, please try again." -msgstr "" +msgstr "Es ist ein Problem aufgetreten, bitte versuche es erneut." #: src/view/com/notifications/FeedItem.tsx:237 #: src/view/com/threadgate/WhoCanReply.tsx:178 @@ -276,7 +254,7 @@ msgstr "und" #: src/screens/Onboarding/index.tsx:32 msgid "Animals" -msgstr "" +msgstr "Tiere" #: src/view/screens/LanguageSettings.tsx:95 msgid "App Language" @@ -284,23 +262,19 @@ msgstr "App-Sprache" #: src/view/screens/AppPasswords.tsx:228 msgid "App password deleted" -msgstr "" +msgstr "App-Passwort gelöscht" #: src/view/com/modals/AddAppPasswords.tsx:134 msgid "App Password names can only contain letters, numbers, spaces, dashes, and underscores." -msgstr "" +msgstr "App-Passwortnamen dürfen nur Buchstaben, Zahlen, Leerzeichen, Bindestriche und Unterstriche enthalten." #: src/view/com/modals/AddAppPasswords.tsx:99 msgid "App Password names must be at least 4 characters long." -msgstr "" +msgstr "App-Passwortnamen müssen mindestens 4 Zeichen lang sein." #: src/view/screens/Settings/index.tsx:675 msgid "App password settings" -msgstr "" - -#: src/view/screens/Settings.tsx:650 -#~ msgid "App passwords" -#~ msgstr "App-Passwörter" +msgstr "App-Passwort-Einstellungen" #: src/Navigation.tsx:239 #: src/view/screens/AppPasswords.tsx:187 @@ -311,11 +285,11 @@ msgstr "App-Passwörter" #: src/view/com/util/forms/PostDropdownBtn.tsx:337 #: src/view/com/util/forms/PostDropdownBtn.tsx:346 msgid "Appeal content warning" -msgstr "" +msgstr "Inhaltswarnungseinspruch" #: src/view/com/modals/AppealLabel.tsx:65 msgid "Appeal Content Warning" -msgstr "" +msgstr "Inhaltswarnungseinspruch" #: src/view/com/util/moderation/LabelInfo.tsx:52 msgid "Appeal this decision" @@ -348,11 +322,11 @@ msgstr "Bist du sicher? Dies kann nicht rückgängig gemacht werden." #: src/view/com/composer/select-language/SuggestedLanguage.tsx:60 msgid "Are you writing in <0>{0}</0>?" -msgstr "" +msgstr "Schreibst du auf <0>{0}</0>?" #: src/screens/Onboarding/index.tsx:26 msgid "Art" -msgstr "" +msgstr "Kunst" #: src/view/com/modals/SelfLabel.tsx:123 msgid "Artistic or non-erotic nudity." @@ -375,11 +349,11 @@ msgstr "Zurück" #: src/view/com/post-thread/PostThread.tsx:480 msgctxt "action" msgid "Back" -msgstr "" +msgstr "Zurück" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:136 msgid "Based on your interest in {interestsText}" -msgstr "" +msgstr "Ausgehend von deinem Interesse an {interestsText}" #: src/view/screens/Settings/index.tsx:523 msgid "Basics" @@ -397,11 +371,11 @@ msgstr "Geburtstag:" #: src/view/com/profile/ProfileHeader.tsx:239 #: src/view/com/profile/ProfileHeader.tsx:346 msgid "Block Account" -msgstr "" +msgstr "Konto blockieren" #: src/view/screens/ProfileList.tsx:556 msgid "Block accounts" -msgstr "" +msgstr "Konten blockieren" #: src/view/screens/ProfileList.tsx:506 msgid "Block list" @@ -413,12 +387,12 @@ msgstr "Diese Konten blockieren?" #: src/view/screens/ProfileList.tsx:320 msgid "Block this List" -msgstr "" +msgstr "Diese Liste blockieren" #: src/view/com/lists/ListCard.tsx:110 #: src/view/com/util/post-embeds/QuoteEmbed.tsx:61 msgid "Blocked" -msgstr "" +msgstr "Blockiert" #: src/view/screens/Moderation.tsx:142 msgid "Blocked accounts" @@ -458,7 +432,7 @@ msgstr "Bluesky" #: src/view/com/auth/server-input/index.tsx:150 msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers." -msgstr "" +msgstr "Bluesky ist ein offenes Netzwerk, in dem du deinen Hosting-Anbieter wählen kannst. Benutzerdefiniertes Hosting ist jetzt in der Beta-Phase für Entwickler verfügbar." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:80 @@ -475,21 +449,13 @@ msgstr "Bluesky ist offen." msgid "Bluesky is public." msgstr "Bluesky ist öffentlich." -#: src/view/com/modals/Waitlist.tsx:70 -#~ msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon." -#~ msgstr "Bluesky nutzt Einladungen, um eine gesündere Community aufzubauen. Wenn du niemanden kennst, der eine Einladung hat, kannst du dich auf die Warteliste setzen lassen und wir schicken dir bald eine zu." - #: src/view/screens/Moderation.tsx:245 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 zeigt dein Profil und deine Beiträge nicht für abgemeldete Nutzer an. Andere Apps kommen dieser Aufforderung möglicherweise nicht nach." -#: src/view/com/modals/ServerInput.tsx:78 -#~ msgid "Bluesky.Social" -#~ msgstr "Bluesky.Social" - #: src/screens/Onboarding/index.tsx:33 msgid "Books" -msgstr "" +msgstr "Bücher" #: src/view/screens/Settings/index.tsx:859 msgid "Build version {0} {1}" @@ -500,25 +466,21 @@ msgstr "Build-Version {0} {1}" msgid "Business" msgstr "Business" -#: src/view/com/modals/ServerInput.tsx:115 -#~ msgid "Button disabled. Input custom domain to proceed." -#~ msgstr "" - #: src/view/com/profile/ProfileSubpageHeader.tsx:157 msgid "by —" -msgstr "" +msgstr "von —" #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:100 msgid "by {0}" -msgstr "" +msgstr "von {0}" #: src/view/com/profile/ProfileSubpageHeader.tsx:161 msgid "by <0/>" -msgstr "" +msgstr "von <0/>" #: src/view/com/profile/ProfileSubpageHeader.tsx:159 msgid "by you" -msgstr "" +msgstr "von dir" #: src/view/com/composer/photos/OpenCameraBtn.tsx:60 #: src/view/com/util/UserAvatar.tsx:224 @@ -557,7 +519,7 @@ msgstr "Abbrechen" #: src/view/com/modals/DeleteAccount.tsx:234 msgctxt "action" msgid "Cancel" -msgstr "" +msgstr "Abbrechen" #: src/view/com/modals/DeleteAccount.tsx:152 #: src/view/com/modals/DeleteAccount.tsx:230 @@ -566,29 +528,25 @@ msgstr "Konto-Löschung abbrechen" #: src/view/com/modals/ChangeHandle.tsx:149 msgid "Cancel change handle" -msgstr "" +msgstr "Handle ändern abbrechen" #: src/view/com/modals/crop-image/CropImage.web.tsx:134 msgid "Cancel image crop" -msgstr "" +msgstr "Bildbeschneidung abbrechen" #: src/view/com/modals/EditProfile.tsx:244 msgid "Cancel profile editing" -msgstr "" +msgstr "Profilbearbeitung abbrechen" #: src/view/com/modals/Repost.tsx:78 msgid "Cancel quote post" -msgstr "" +msgstr "Beitrag zitieren abbrechen" #: src/view/com/modals/ListAddRemoveUsers.tsx:87 #: src/view/shell/desktop/Search.tsx:234 msgid "Cancel search" msgstr "Suche abbrechen" -#: src/view/com/modals/Waitlist.tsx:136 -#~ msgid "Cancel waitlist signup" -#~ msgstr "Anmeldung zur Warteliste abbrechen" - #: src/view/screens/Settings/index.tsx:334 msgctxt "action" msgid "Change" @@ -609,19 +567,19 @@ msgstr "Meine E-Mail ändern" #: src/view/screens/Settings/index.tsx:732 msgid "Change password" -msgstr "" +msgstr "Passwort ändern" #: src/view/screens/Settings/index.tsx:741 msgid "Change Password" -msgstr "" +msgstr "Passwort Ändern" #: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 msgid "Change post language to {0}" -msgstr "" +msgstr "Beitragssprache in {0} ändern" #: src/view/screens/Settings/index.tsx:733 msgid "Change your Bluesky password" -msgstr "" +msgstr "Ändere dein Bluesky-Passwort" #: src/view/com/modals/ChangeEmail.tsx:109 msgid "Change Your Email" @@ -630,7 +588,7 @@ msgstr "Deine E-Mail ändern" #: src/screens/Deactivated.tsx:72 #: src/screens/Deactivated.tsx:76 msgid "Check my status" -msgstr "" +msgstr "Meinen Status prüfen" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:121 msgid "Check out some recommended feeds. Tap + to add them to your list of pinned feeds." @@ -646,11 +604,11 @@ msgstr "Überprüfe deinen Posteingang auf eine E-Mail mit dem Bestätigungscode #: src/view/com/modals/Threadgate.tsx:72 msgid "Choose \"Everybody\" or \"Nobody\"" -msgstr "" +msgstr "Wähle \"Alle\" oder \"Niemand\"" #: src/view/screens/Settings/index.tsx:697 msgid "Choose a new Bluesky username or create" -msgstr "" +msgstr "Wähle oder erstelle einen neuen Bluesky-Benutzernamen" #: src/view/com/auth/server-input/index.tsx:79 msgid "Choose Service" @@ -658,20 +616,16 @@ msgstr "Service wählen" #: src/screens/Onboarding/StepFinished.tsx:135 msgid "Choose the algorithms that power your custom feeds." -msgstr "" +msgstr "Wähle die Algorithmen aus, welche deine benutzerdefinierten Feeds generieren." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:83 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:83 msgid "Choose the algorithms that power your experience with custom feeds." -msgstr "" - -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 -#~ msgid "Choose your algorithmic feeds" -#~ msgstr "" +msgstr "Wähle die Algorithmen aus, welche dein Erlebnis mit benutzerdefinierten Feeds unterstützen." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 msgid "Choose your main feeds" -msgstr "" +msgstr "Wähle deine Haupt-Feeds" #: src/view/com/auth/create/Step1.tsx:196 msgid "Choose your password" @@ -702,37 +656,37 @@ msgstr "Suchanfrage löschen" #: src/view/screens/Support.tsx:40 msgid "click here" -msgstr "" +msgstr "hier klicken" #: src/components/TagMenu/index.web.tsx:138 msgid "Click here to open tag menu for {tag}" -msgstr "" +msgstr "Klicke hier, um das Tag-Menü für {tag} zu öffnen" #: src/components/RichText.tsx:191 msgid "Click here to open tag menu for #{tag}" -msgstr "" +msgstr "Klicke hier, um das Tag-Menü für #{tag} zu öffnen" #: src/screens/Onboarding/index.tsx:35 msgid "Climate" -msgstr "" +msgstr "Klima" #: src/view/com/modals/ChangePassword.tsx:265 #: src/view/com/modals/ChangePassword.tsx:268 msgid "Close" -msgstr "" +msgstr "Schließen" #: src/components/Dialog/index.web.tsx:84 #: src/components/Dialog/index.web.tsx:198 msgid "Close active dialog" -msgstr "" +msgstr "Aktiven Dialog schließen" #: src/view/com/auth/login/PasswordUpdatedForm.tsx:38 msgid "Close alert" -msgstr "" +msgstr "Meldung schließen" #: src/view/com/util/BottomSheetCustomBackdrop.tsx:33 msgid "Close bottom drawer" -msgstr "" +msgstr "Untere Schublade schließen" #: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:26 msgid "Close image" @@ -740,43 +694,43 @@ msgstr "Bild schließen" #: src/view/com/lightbox/Lightbox.web.tsx:119 msgid "Close image viewer" -msgstr "" +msgstr "Bildbetrachter schließen" #: src/view/shell/index.web.tsx:51 msgid "Close navigation footer" -msgstr "" +msgstr "Fußzeile der Navigation schließen" #: src/components/TagMenu/index.tsx:262 msgid "Close this dialog" -msgstr "" +msgstr "Diesen Dialog schließen" #: src/view/shell/index.web.tsx:52 msgid "Closes bottom navigation bar" -msgstr "" +msgstr "Schließt die untere Navigationsleiste" #: src/view/com/auth/login/PasswordUpdatedForm.tsx:39 msgid "Closes password update alert" -msgstr "" +msgstr "Schließt die Kennwortaktualisierungsmeldung" #: src/view/com/composer/Composer.tsx:309 msgid "Closes post composer and discards post draft" -msgstr "" +msgstr "Schließt den Beitragsverfasser und verwirft den Beitragsentwurf" #: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:27 msgid "Closes viewer for header image" -msgstr "" +msgstr "Schließt den Betrachter für das Banner" #: src/view/com/notifications/FeedItem.tsx:318 msgid "Collapses list of users for a given notification" -msgstr "" +msgstr "Klappt die Liste der Benutzer für eine bestimmte Meldung zusammen" #: src/screens/Onboarding/index.tsx:41 msgid "Comedy" -msgstr "" +msgstr "Komödie" #: src/screens/Onboarding/index.tsx:27 msgid "Comics" -msgstr "" +msgstr "Comics" #: src/Navigation.tsx:229 #: src/view/screens/CommunityGuidelines.tsx:32 @@ -785,15 +739,15 @@ msgstr "Community-Richtlinien" #: src/screens/Onboarding/StepFinished.tsx:148 msgid "Complete onboarding and start using your account" -msgstr "" +msgstr "Schließe das Onboarding ab und nutze dein Konto" #: src/view/com/auth/create/Step3.tsx:73 msgid "Complete the challenge" -msgstr "" +msgstr "Beende die Herausforderung" #: src/view/com/composer/Composer.tsx:424 msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" -msgstr "" +msgstr "Verfasse Beiträge mit einer Länge von bis zu {MAX_GRAPHEME_LENGTH} Zeichen" #: src/view/com/composer/Prompt.tsx:24 msgid "Compose reply" @@ -801,7 +755,7 @@ msgstr "Antwort verfassen" #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:67 msgid "Configure content filtering setting for category: {0}" -msgstr "" +msgstr "Inhaltsfilterungseinstellung der Kategorie {0} konfigurieren" #: src/components/Prompt.tsx:124 #: src/view/com/modals/AppealLabel.tsx:98 @@ -817,7 +771,7 @@ msgstr "Bestätigen" #: src/view/com/modals/Confirm.tsx:78 msgctxt "action" msgid "Confirm" -msgstr "" +msgstr "Bestätigen" #: src/view/com/modals/ChangeEmail.tsx:193 #: src/view/com/modals/ChangeEmail.tsx:195 @@ -834,7 +788,7 @@ msgstr "Bestätige das Löschen des Kontos" #: src/view/com/modals/ContentFilteringSettings.tsx:156 msgid "Confirm your age to enable adult content." -msgstr "" +msgstr "Bestätige dein Alter, um Inhalte für Erwachsene zu aktivieren." #: src/view/com/modals/ChangeEmail.tsx:157 #: src/view/com/modals/DeleteAccount.tsx:182 @@ -842,10 +796,6 @@ msgstr "" msgid "Confirmation code" msgstr "Bestätigungscode" -#: src/view/com/modals/Waitlist.tsx:120 -#~ msgid "Confirms signing up {email} to the waitlist" -#~ msgstr "" - #: src/view/com/auth/create/CreateAccount.tsx:193 #: src/view/com/auth/login/LoginForm.tsx:278 msgid "Connecting..." @@ -853,7 +803,7 @@ msgstr "Verbinden..." #: src/view/com/auth/create/CreateAccount.tsx:213 msgid "Contact support" -msgstr "" +msgstr "Support kontaktieren" #: src/view/screens/Moderation.tsx:83 msgid "Content filtering" @@ -866,11 +816,11 @@ msgstr "Inhaltsfilterung" #: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:74 #: src/view/screens/LanguageSettings.tsx:278 msgid "Content Languages" -msgstr "" +msgstr "Inhaltssprachen" #: src/view/com/modals/ModerationDetails.tsx:65 msgid "Content Not Available" -msgstr "" +msgstr "Inhalt nicht verfügbar" #: src/view/com/modals/ModerationDetails.tsx:33 #: src/view/com/util/moderation/ScreenHider.tsx:78 @@ -896,19 +846,19 @@ msgstr "Fortfahren" #: src/screens/Onboarding/StepModeration/index.tsx:115 #: src/screens/Onboarding/StepTopicalFeeds.tsx:111 msgid "Continue to next step" -msgstr "" +msgstr "Weiter zum nächsten Schritt" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:167 msgid "Continue to the next step" -msgstr "" +msgstr "Weiter zum nächsten Schritt" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:191 msgid "Continue to the next step without following any accounts" -msgstr "" +msgstr "Fahre mit dem nächsten Schritt fort, ohne Konten zu folgen" #: src/screens/Onboarding/index.tsx:44 msgid "Cooking" -msgstr "" +msgstr "Kochen" #: src/view/com/modals/AddAppPasswords.tsx:195 #: src/view/com/modals/InviteCodes.tsx:182 @@ -917,17 +867,17 @@ msgstr "Kopiert" #: src/view/screens/Settings/index.tsx:241 msgid "Copied build version to clipboard" -msgstr "" +msgstr "Die Build-Version wurde in die Zwischenablage kopiert" #: src/view/com/modals/AddAppPasswords.tsx:76 #: src/view/com/modals/InviteCodes.tsx:152 #: src/view/com/util/forms/PostDropdownBtn.tsx:161 msgid "Copied to clipboard" -msgstr "" +msgstr "In die Zwischenablage kopiert" #: src/view/com/modals/AddAppPasswords.tsx:189 msgid "Copies app password" -msgstr "" +msgstr "Kopiert das App-Passwort" #: src/view/com/modals/AddAppPasswords.tsx:188 msgid "Copy" @@ -949,12 +899,12 @@ msgstr "Link zum Profil kopieren" #: src/view/com/util/forms/PostDropdownBtn.tsx:223 #: src/view/com/util/forms/PostDropdownBtn.tsx:225 msgid "Copy post text" -msgstr "" +msgstr "Beitragstext kopieren" #: src/Navigation.tsx:234 #: src/view/screens/CopyrightPolicy.tsx:29 msgid "Copyright Policy" -msgstr "" +msgstr "Urheberrechtsbestimmungen" #: src/view/screens/ProfileFeed.tsx:97 msgid "Could not load feed" @@ -964,10 +914,6 @@ msgstr "Feed konnte nicht geladen werden" msgid "Could not load list" msgstr "Liste konnte nicht geladen werden" -#: src/view/com/auth/create/Step2.tsx:91 -#~ msgid "Country" -#~ msgstr "" - #: src/view/com/auth/HomeLoggedOutCTA.tsx:62 #: src/view/com/auth/SplashScreen.tsx:71 #: src/view/com/auth/SplashScreen.web.tsx:81 @@ -976,7 +922,7 @@ msgstr "Ein neues Konto erstellen" #: src/view/screens/Settings/index.tsx:384 msgid "Create a new Bluesky account" -msgstr "" +msgstr "Erstelle ein neues Bluesky-Konto" #: src/view/com/auth/create/CreateAccount.tsx:133 msgid "Create Account" @@ -984,7 +930,7 @@ msgstr "Konto erstellen" #: src/view/com/modals/AddAppPasswords.tsx:226 msgid "Create App Password" -msgstr "" +msgstr "App-Passwort erstellen" #: src/view/com/auth/HomeLoggedOutCTA.tsx:54 #: src/view/com/auth/SplashScreen.tsx:68 @@ -997,24 +943,24 @@ msgstr "Erstellt {0}" #: src/view/screens/ProfileFeed.tsx:616 msgid "Created by <0/>" -msgstr "" +msgstr "Erstellt von <0/>" #: src/view/screens/ProfileFeed.tsx:614 msgid "Created by you" -msgstr "" +msgstr "Erstellt von dir" #: src/view/com/composer/Composer.tsx:455 msgid "Creates a card with a thumbnail. The card links to {url}" -msgstr "" +msgstr "Erzeugt eine Karte mit Vorschaubild und verlinkt auf {url}" #: src/screens/Onboarding/index.tsx:29 msgid "Culture" -msgstr "" +msgstr "Kultur" #: src/view/com/auth/server-input/index.tsx:95 #: src/view/com/auth/server-input/index.tsx:96 msgid "Custom" -msgstr "" +msgstr "Benutzerdefiniert" #: src/view/com/modals/ChangeHandle.tsx:389 msgid "Custom domain" @@ -1023,32 +969,28 @@ msgstr "Benutzerdefinierte Domain" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 #: src/view/screens/Feeds.tsx:692 msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." -msgstr "" +msgstr "Benutzerdefinierte Feeds, die von der Community erstellt wurden, bringen dir neue Erfahrungen und helfen dir, die Inhalte zu finden, die du liebst." #: src/view/screens/PreferencesExternalEmbeds.tsx:55 msgid "Customize media from external sites." -msgstr "" - -#: src/view/screens/Settings.tsx:687 -#~ msgid "Danger Zone" -#~ msgstr "" +msgstr "Passe die Einstellungen für Medien von externen Websites an." #: src/view/screens/Settings/index.tsx:485 #: src/view/screens/Settings/index.tsx:511 msgid "Dark" -msgstr "" +msgstr "Dunkel" #: src/view/screens/Debug.tsx:63 msgid "Dark mode" -msgstr "" +msgstr "Dunkelmodus" #: src/view/screens/Settings/index.tsx:498 msgid "Dark Theme" -msgstr "" +msgstr "Dunkles Thema" #: src/view/screens/Debug.tsx:83 msgid "Debug panel" -msgstr "" +msgstr "Debug-Panel" #: src/view/screens/Settings/index.tsx:772 msgid "Delete account" @@ -1072,13 +1014,9 @@ msgstr "Liste löschen" msgid "Delete my account" msgstr "Mein Konto löschen" -#: src/view/screens/Settings.tsx:706 -#~ msgid "Delete my account…" -#~ msgstr "Mein Konto löschen…" - #: src/view/screens/Settings/index.tsx:784 msgid "Delete My Account…" -msgstr "" +msgstr "Mein Konto Löschen…" #: src/view/com/util/forms/PostDropdownBtn.tsx:317 #: src/view/com/util/forms/PostDropdownBtn.tsx:326 @@ -1091,7 +1029,7 @@ msgstr "Diesen Beitrag löschen?" #: src/view/com/util/post-embeds/QuoteEmbed.tsx:70 msgid "Deleted" -msgstr "" +msgstr "Gelöscht" #: src/view/com/post-thread/PostThread.tsx:316 msgid "Deleted post." @@ -1104,17 +1042,13 @@ msgstr "Gelöschter Beitrag." msgid "Description" msgstr "Beschreibung" -#: src/view/screens/Settings.tsx:760 -#~ msgid "Developer Tools" -#~ msgstr "Entwickler-Tools" - #: src/view/com/composer/Composer.tsx:218 msgid "Did you want to say anything?" -msgstr "" +msgstr "Wolltest du etwas sagen?" #: src/view/screens/Settings/index.tsx:504 msgid "Dim" -msgstr "" +msgstr "Dimmen" #: src/view/com/composer/Composer.tsx:151 msgid "Discard" @@ -1131,15 +1065,11 @@ msgstr "Apps daran hindern, abgemeldeten Nutzern mein Konto zu zeigen" #: src/view/com/posts/FollowingEmptyState.tsx:74 #: src/view/com/posts/FollowingEndOfFeed.tsx:75 msgid "Discover new custom feeds" -msgstr "" - -#: src/view/screens/Feeds.tsx:473 -#~ msgid "Discover new feeds" -#~ msgstr "Entdecke neue Feeds" +msgstr "Entdecke neue benutzerdefinierte Feeds" #: src/view/screens/Feeds.tsx:689 msgid "Discover New Feeds" -msgstr "" +msgstr "Entdecke neue Feeds" #: src/view/com/modals/EditProfile.tsx:192 msgid "Display name" @@ -1153,10 +1083,6 @@ msgstr "Anzeigename" msgid "Domain verified!" msgstr "Domain verifiziert!" -#: src/view/com/auth/create/Step1.tsx:170 -#~ msgid "Don't have an invite code?" -#~ msgstr "" - #: src/view/com/auth/onboarding/RecommendedFollows.tsx:86 #: src/view/com/modals/EditImage.tsx:333 #: src/view/com/modals/ListAddRemoveUsers.tsx:144 @@ -1168,7 +1094,7 @@ msgstr "Domain verifiziert!" #: src/view/screens/PreferencesThreads.tsx:162 msgctxt "action" msgid "Done" -msgstr "" +msgstr "Erledigt" #: src/view/com/auth/server-input/index.tsx:165 #: src/view/com/auth/server-input/index.tsx:166 @@ -1192,48 +1118,48 @@ msgstr "Erledigt{extraText}" #: src/view/com/auth/login/ChooseAccountForm.tsx:45 msgid "Double tap to sign in" -msgstr "" +msgstr "Doppeltippen zum Anmelden" #: src/view/screens/Settings/index.tsx:755 msgid "Download Bluesky account data (repository)" -msgstr "" +msgstr "Öffnet ein Modal zum Herunterladen deiner Bluesky-Kontodaten (Kontodepot)" #: src/view/screens/Settings/ExportCarDialog.tsx:59 #: src/view/screens/Settings/ExportCarDialog.tsx:63 msgid "Download CAR file" -msgstr "" +msgstr "CAR-Datei herunterladen" #: src/view/com/composer/text-input/TextInput.web.tsx:249 msgid "Drop to add images" -msgstr "" +msgstr "Ablegen zum Hinzufügen von Bildern" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:111 msgid "Due to Apple policies, adult content can only be enabled on the web after completing sign up." -msgstr "" +msgstr "Aufgrund der Apple-Richtlinien können Inhalte für Erwachsene erst nach Abschluss der Registrierung auf der Website aktiviert werden." #: src/view/com/modals/EditProfile.tsx:185 msgid "e.g. Alice Roberts" -msgstr "" +msgstr "z.B. Alice Roberts" #: src/view/com/modals/EditProfile.tsx:203 msgid "e.g. Artist, dog-lover, and avid reader." -msgstr "" +msgstr "z.B. Künstlerin, Hundeliebhaberin und begeisterte Leserin." #: src/view/com/modals/CreateOrEditList.tsx:283 msgid "e.g. Great Posters" -msgstr "" +msgstr "z.B. Große Poster" #: src/view/com/modals/CreateOrEditList.tsx:284 msgid "e.g. Spammers" -msgstr "" +msgstr "z.B. Spammer" #: src/view/com/modals/CreateOrEditList.tsx:312 msgid "e.g. The posters who never miss." -msgstr "" +msgstr "z.B. Die Poster, die immer ins Schwarze treffen." #: src/view/com/modals/CreateOrEditList.tsx:313 msgid "e.g. Users that repeatedly reply with ads." -msgstr "" +msgstr "z.B. Nutzer, die wiederholt mit Werbung antworten." #: src/view/com/modals/InviteCodes.tsx:96 msgid "Each code works once. You'll receive more invite codes periodically." @@ -1242,7 +1168,7 @@ msgstr "Jeder Code funktioniert einmal. Du erhältst regelmäßig neue Einladung #: src/view/com/lists/ListMembers.tsx:149 msgctxt "action" msgid "Edit" -msgstr "" +msgstr "Bearbeiten" #: src/view/com/composer/photos/Gallery.tsx:144 #: src/view/com/modals/EditImage.tsx:207 @@ -1255,7 +1181,7 @@ msgstr "Details der Liste bearbeiten" #: src/view/com/modals/CreateOrEditList.tsx:250 msgid "Edit Moderation List" -msgstr "" +msgstr "Moderationsliste bearbeiten" #: src/Navigation.tsx:244 #: src/view/screens/Feeds.tsx:434 @@ -1282,19 +1208,19 @@ msgstr "Gespeicherte Feeds bearbeiten" #: src/view/com/modals/CreateOrEditList.tsx:245 msgid "Edit User List" -msgstr "" +msgstr "Benutzerliste bearbeiten" #: src/view/com/modals/EditProfile.tsx:193 msgid "Edit your display name" -msgstr "" +msgstr "Bearbeite deinen Anzeigenamen" #: src/view/com/modals/EditProfile.tsx:211 msgid "Edit your profile description" -msgstr "" +msgstr "Bearbeite deine Profilbeschreibung" #: src/screens/Onboarding/index.tsx:34 msgid "Education" -msgstr "" +msgstr "Bildung" #: src/view/com/auth/create/Step1.tsx:176 #: src/view/com/auth/login/ForgotPasswordForm.tsx:156 @@ -1310,7 +1236,7 @@ msgstr "E-Mail-Adresse" #: src/view/com/modals/ChangeEmail.tsx:56 #: src/view/com/modals/ChangeEmail.tsx:88 msgid "Email updated" -msgstr "" +msgstr "E-Mail aktualisiert" #: src/view/com/modals/ChangeEmail.tsx:111 msgid "Email Updated" @@ -1318,7 +1244,7 @@ msgstr "E-Mail aktualisiert" #: src/view/com/modals/VerifyEmail.tsx:78 msgid "Email verified" -msgstr "" +msgstr "E-Mail verifiziert" #: src/view/screens/Settings/index.tsx:312 msgid "Email:" @@ -1326,24 +1252,24 @@ msgstr "E-Mail:" #: src/view/com/modals/EmbedConsent.tsx:113 msgid "Enable {0} only" -msgstr "" +msgstr "Nur {0} aktivieren" #: src/view/com/modals/ContentFilteringSettings.tsx:167 msgid "Enable Adult Content" -msgstr "" +msgstr "Inhalte für Erwachsene aktivieren" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:76 #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:77 msgid "Enable adult content in your feeds" -msgstr "" +msgstr "Aktiviere Inhalte für Erwachsene in deinen Feeds" #: src/view/com/modals/EmbedConsent.tsx:97 msgid "Enable External Media" -msgstr "" +msgstr "Externe Medien aktivieren" #: src/view/screens/PreferencesExternalEmbeds.tsx:75 msgid "Enable media players for" -msgstr "" +msgstr "Aktiviere Medienplayer für" #: src/view/screens/PreferencesFollowingFeed.tsx:147 msgid "Enable this setting to only see replies between people you follow." @@ -1355,20 +1281,20 @@ msgstr "Ende des Feeds" #: src/view/com/modals/AddAppPasswords.tsx:166 msgid "Enter a name for this App Password" -msgstr "" +msgstr "Gebe einen Namen für dieses App-Passwort ein" #: src/components/dialogs/MutedWords.tsx:100 #: src/components/dialogs/MutedWords.tsx:101 msgid "Enter a word or tag" -msgstr "" +msgstr "Gib ein Wort oder einen Tag ein" #: src/view/com/modals/VerifyEmail.tsx:105 msgid "Enter Confirmation Code" -msgstr "" +msgstr "Bestätigungscode eingeben" #: src/view/com/modals/ChangePassword.tsx:151 msgid "Enter the code you received to change your password." -msgstr "" +msgstr "Gib den Code ein, welchen du erhalten hast, um dein Passwort zu ändern." #: src/view/com/modals/ChangeHandle.tsx:371 msgid "Enter the domain you want to use" @@ -1381,11 +1307,7 @@ msgstr "Gib die E-Mail ein, die du zur Erstellung deines Kontos verwendet hast. #: src/view/com/auth/create/Step1.tsx:228 #: src/view/com/modals/BirthDateSettings.tsx:74 msgid "Enter your birth date" -msgstr "" - -#: src/view/com/modals/Waitlist.tsx:78 -#~ msgid "Enter your email" -#~ msgstr "" +msgstr "Gib dein Geburtsdatum ein" #: src/view/com/auth/create/Step1.tsx:172 msgid "Enter your email address" @@ -1393,23 +1315,19 @@ msgstr "Gib deine E-Mail-Adresse ein" #: src/view/com/modals/ChangeEmail.tsx:41 msgid "Enter your new email above" -msgstr "" +msgstr "Gib oben deine neue E-Mail-Adresse ein" #: src/view/com/modals/ChangeEmail.tsx:117 msgid "Enter your new email address below." msgstr "Gib unten deine neue E-Mail-Adresse ein." -#: src/view/com/auth/create/Step2.tsx:188 -#~ msgid "Enter your phone number" -#~ msgstr "" - #: src/view/com/auth/login/Login.tsx:99 msgid "Enter your username and password" msgstr "Gib deinen Benutzernamen und dein Passwort ein" #: src/view/com/auth/create/Step3.tsx:67 msgid "Error receiving captcha response." -msgstr "" +msgstr "Fehler beim Empfang der Captcha-Antwort." #: src/view/screens/Search/Search.tsx:110 msgid "Error:" @@ -1421,20 +1339,16 @@ msgstr "Alle" #: src/view/com/modals/ChangeHandle.tsx:150 msgid "Exits handle change process" -msgstr "" +msgstr "Beendet den Prozess des Handle-Wechsels" #: src/view/com/lightbox/Lightbox.web.tsx:120 msgid "Exits image view" -msgstr "" +msgstr "Beendet die Bildansicht" #: src/view/com/modals/ListAddRemoveUsers.tsx:88 #: src/view/shell/desktop/Search.tsx:235 msgid "Exits inputting search query" -msgstr "" - -#: src/view/com/modals/Waitlist.tsx:138 -#~ msgid "Exits signing up for waitlist with {email}" -#~ msgstr "" +msgstr "Beendet die Eingabe der Suchanfrage" #: src/view/com/lightbox/Lightbox.web.tsx:163 msgid "Expand alt text" @@ -1443,48 +1357,48 @@ msgstr "Alt-Text erweitern" #: src/view/com/composer/ComposerReplyTo.tsx:81 #: src/view/com/composer/ComposerReplyTo.tsx:84 msgid "Expand or collapse the full post you are replying to" -msgstr "" +msgstr "Erweitere oder reduziere den gesamten Beitrag, auf den du antwortest" #: src/view/screens/Settings/index.tsx:753 msgid "Export my data" -msgstr "" +msgstr "Exportiere meine Daten" #: src/view/screens/Settings/ExportCarDialog.tsx:44 #: src/view/screens/Settings/index.tsx:764 msgid "Export My Data" -msgstr "" +msgstr "Exportiere meine Daten" #: src/view/com/modals/EmbedConsent.tsx:64 msgid "External Media" -msgstr "" +msgstr "Externe Medien" #: src/view/com/modals/EmbedConsent.tsx:75 #: src/view/screens/PreferencesExternalEmbeds.tsx:66 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 "" +msgstr "Externe Medien können es Websites ermöglichen, Informationen über dich und dein Gerät zu sammeln. Es werden keine Informationen gesendet oder angefordert, bis du die Schaltfläche \"Abspielen\" drückst." #: src/Navigation.tsx:263 #: src/view/screens/PreferencesExternalEmbeds.tsx:52 #: src/view/screens/Settings/index.tsx:657 msgid "External Media Preferences" -msgstr "" +msgstr "Externe Medienpräferenzen" #: src/view/screens/Settings/index.tsx:648 msgid "External media settings" -msgstr "" +msgstr "Externe Medienpräferenzen" #: src/view/com/modals/AddAppPasswords.tsx:115 #: src/view/com/modals/AddAppPasswords.tsx:119 msgid "Failed to create app password." -msgstr "" +msgstr "Das App-Passwort konnte nicht erstellt werden." #: src/view/com/modals/CreateOrEditList.tsx:206 msgid "Failed to create the list. Check your internet connection and try again." -msgstr "" +msgstr "Die Liste konnte nicht erstellt werden. Überprüfe deine Internetverbindung und versuche es erneut." #: src/view/com/util/forms/PostDropdownBtn.tsx:128 msgid "Failed to delete post, please try again" -msgstr "" +msgstr "Beitrag konnte nicht gelöscht werden, bitte versuche es erneut" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:109 #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:141 @@ -1493,20 +1407,16 @@ msgstr "Empfohlene Feeds konnten nicht geladen werden" #: src/Navigation.tsx:194 msgid "Feed" -msgstr "" +msgstr "Feed" #: src/view/com/feeds/FeedSourceCard.tsx:231 msgid "Feed by {0}" -msgstr "" +msgstr "Feed von {0}" #: src/view/screens/Feeds.tsx:605 msgid "Feed offline" msgstr "Feed offline" -#: src/view/com/feeds/FeedPage.tsx:143 -#~ msgid "Feed Preferences" -#~ msgstr "Feed-Einstellungen" - #: src/view/shell/desktop/RightNav.tsx:61 #: src/view/shell/Drawer.tsx:311 msgid "Feedback" @@ -1523,14 +1433,6 @@ msgstr "Feedback" msgid "Feeds" msgstr "Feeds" -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 -#~ msgid "Feeds are created by users and can give you entirely new experiences." -#~ msgstr "" - -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 -#~ msgid "Feeds are created by users and organizations. They offer you varied experiences and suggest content you may like using algorithms." -#~ msgstr "" - #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:57 msgid "Feeds are created by users to curate content. Choose some feeds that you find interesting." msgstr "Feeds werden von Nutzern erstellt, um Inhalte zu kuratieren. Wähle einige Feeds aus, die du interessant findest." @@ -1541,17 +1443,17 @@ msgstr "Feeds sind benutzerdefinierte Algorithmen, die Nutzer mit ein wenig Prog #: src/screens/Onboarding/StepTopicalFeeds.tsx:76 msgid "Feeds can be topical as well!" -msgstr "" +msgstr "Die Feeds können auch auf einem Thema basieren!" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Finalizing" -msgstr "" +msgstr "Abschließen" #: src/view/com/posts/CustomFeedEmptyState.tsx:47 #: src/view/com/posts/FollowingEmptyState.tsx:57 #: src/view/com/posts/FollowingEndOfFeed.tsx:58 msgid "Find accounts to follow" -msgstr "" +msgstr "Konten zum Folgen finden" #: src/view/screens/Search/Search.tsx:440 msgid "Find users on Bluesky" @@ -1567,32 +1469,28 @@ msgstr "Suche nach ähnlichen Konten..." #: src/view/screens/PreferencesFollowingFeed.tsx:111 msgid "Fine-tune the content you see on your Following feed." -msgstr "" - -#: src/view/screens/PreferencesHomeFeed.tsx:111 -#~ msgid "Fine-tune the content you see on your home screen." -#~ msgstr "" +msgstr "Passe die Inhalte auf Deinem Following-Feed an." #: src/view/screens/PreferencesThreads.tsx:60 msgid "Fine-tune the discussion threads." -msgstr "" +msgstr "Passe die Diskussionsstränge an." #: src/screens/Onboarding/index.tsx:38 msgid "Fitness" -msgstr "" +msgstr "Fitness" #: src/screens/Onboarding/StepFinished.tsx:131 msgid "Flexible" -msgstr "" +msgstr "Flexibel" #: src/view/com/modals/EditImage.tsx:115 msgid "Flip horizontal" -msgstr "" +msgstr "Horizontal drehen" #: src/view/com/modals/EditImage.tsx:120 #: src/view/com/modals/EditImage.tsx:287 msgid "Flip vertically" -msgstr "" +msgstr "Vertikal drehen" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:181 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:136 @@ -1603,21 +1501,21 @@ msgstr "Folgen" #: src/view/com/profile/FollowButton.tsx:64 msgctxt "action" msgid "Follow" -msgstr "" +msgstr "Folgen" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:58 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:122 #: src/view/com/profile/ProfileHeader.tsx:504 msgid "Follow {0}" -msgstr "" +msgstr "{0} folgen" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:179 msgid "Follow All" -msgstr "" +msgstr "Allen folgen" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:174 msgid "Follow selected accounts and continue to the next step" -msgstr "" +msgstr "Ausgewählten Konten folgen und mit dem nächsten Schritt fortfahren" #: src/view/com/auth/onboarding/RecommendedFollows.tsx:64 msgid "Follow some users to get started. We can recommend you more users based on who you find interesting." @@ -1625,19 +1523,19 @@ msgstr "Folge einigen Nutzern, um loszulegen. Wir können dir weitere Nutzer emp #: src/view/com/profile/ProfileCard.tsx:194 msgid "Followed by {0}" -msgstr "" +msgstr "Gefolgt von {0}" #: src/view/com/modals/Threadgate.tsx:98 msgid "Followed users" -msgstr "" +msgstr "Benutzer, denen ich folge" #: src/view/screens/PreferencesFollowingFeed.tsx:154 msgid "Followed users only" -msgstr "" +msgstr "Nur Benutzer, denen ich folge" #: src/view/com/notifications/FeedItem.tsx:166 msgid "followed you" -msgstr "" +msgstr "folgte dir" #: src/view/screens/ProfileFollowers.tsx:25 msgid "Followers" @@ -1651,7 +1549,7 @@ msgstr "Folge ich" #: src/view/com/profile/ProfileHeader.tsx:149 msgid "Following {0}" -msgstr "" +msgstr "ich folge {0}" #: src/Navigation.tsx:250 #: src/view/com/home/HomeHeaderLayout.web.tsx:50 @@ -1659,7 +1557,7 @@ msgstr "" #: src/view/screens/PreferencesFollowingFeed.tsx:104 #: src/view/screens/Settings/index.tsx:543 msgid "Following Feed Preferences" -msgstr "" +msgstr "Following-Feed-Einstellungen" #: src/view/com/profile/ProfileHeader.tsx:546 msgid "Follows you" @@ -1667,11 +1565,11 @@ msgstr "Folgt dir" #: src/view/com/profile/ProfileCard.tsx:141 msgid "Follows You" -msgstr "" +msgstr "Folgt dir" #: src/screens/Onboarding/index.tsx:43 msgid "Food" -msgstr "" +msgstr "Essen" #: src/view/com/modals/DeleteAccount.tsx:111 msgid "For security reasons, we'll need to send a confirmation code to your email address." @@ -1697,12 +1595,12 @@ msgstr "Passwort vergessen" #: src/screens/Hashtag.tsx:108 #: src/screens/Hashtag.tsx:148 msgid "From @{sanitizedAuthor}" -msgstr "" +msgstr "Von @{sanitizedAuthor}" #: src/view/com/posts/FeedItem.tsx:189 msgctxt "from-feed" msgid "From <0/>" -msgstr "" +msgstr "Aus <0/>" #: src/view/com/composer/photos/SelectPhotoBtn.tsx:43 msgid "Gallery" @@ -1730,12 +1628,12 @@ msgstr "Gehe zurück" #: src/screens/Onboarding/Layout.tsx:104 #: src/screens/Onboarding/Layout.tsx:193 msgid "Go back to previous step" -msgstr "" +msgstr "Zum vorherigen Schritt zurückkehren" #: src/view/screens/Search/Search.tsx:747 #: src/view/shell/desktop/Search.tsx:262 msgid "Go to @{queryMaybeHandle}" -msgstr "" +msgstr "Gehe zu @{queryMaybeHandle}" #: src/view/com/auth/login/ForgotPasswordForm.tsx:189 #: src/view/com/auth/login/ForgotPasswordForm.tsx:218 @@ -1751,19 +1649,15 @@ msgstr "Handle" #: src/Navigation.tsx:270 msgid "Hashtag" -msgstr "" - -#: src/components/RichText.tsx:188 -#~ msgid "Hashtag: {tag}" -#~ msgstr "" +msgstr "Hashtag" #: src/components/RichText.tsx:190 msgid "Hashtag: #{tag}" -msgstr "" +msgstr "Hashtag: #{tag}" #: src/view/com/auth/create/CreateAccount.tsx:208 msgid "Having trouble?" -msgstr "" +msgstr "Hast du Probleme?" #: src/view/shell/desktop/RightNav.tsx:90 #: src/view/shell/Drawer.tsx:321 @@ -1772,15 +1666,15 @@ msgstr "Hilfe" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:132 msgid "Here are some accounts for you to follow" -msgstr "" +msgstr "Hier sind einige Konten, denen du folgen könntest" #: src/screens/Onboarding/StepTopicalFeeds.tsx:85 msgid "Here are some popular topical feeds. You can choose to follow as many as you like." -msgstr "" +msgstr "Hier sind einige beliebte thematische Feeds. Du kannst so vielen folgen, wie du möchtest." #: src/screens/Onboarding/StepTopicalFeeds.tsx:80 msgid "Here are some topical feeds based on your interests: {interestsText}. You can choose to follow as many as you like." -msgstr "" +msgstr "Hier sind einige thematische Feeds, die auf deinen Interessen basieren: {interestsText}. Du kannst so vielen Feeds folgen, wie du möchtest." #: src/view/com/modals/AddAppPasswords.tsx:153 msgid "Here is your app password." @@ -1797,7 +1691,7 @@ msgstr "Ausblenden" #: src/view/com/notifications/FeedItem.tsx:326 msgctxt "action" msgid "Hide" -msgstr "" +msgstr "Ausblenden" #: src/view/com/util/forms/PostDropdownBtn.tsx:276 #: src/view/com/util/forms/PostDropdownBtn.tsx:287 @@ -1807,7 +1701,7 @@ msgstr "Beitrag ausblenden" #: src/view/com/util/moderation/ContentHider.tsx:67 #: src/view/com/util/moderation/PostHider.tsx:61 msgid "Hide the content" -msgstr "" +msgstr "Den Inhalt ausblenden" #: src/view/com/util/forms/PostDropdownBtn.tsx:280 msgid "Hide this post?" @@ -1819,19 +1713,19 @@ msgstr "Benutzerliste ausblenden" #: src/view/com/profile/ProfileHeader.tsx:487 msgid "Hides posts from {0} in your feed" -msgstr "" +msgstr "Blendet Beiträge von {0} in Deinem Feed aus" #: src/view/com/posts/FeedErrorMessage.tsx:111 msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." -msgstr "Hm, beim Kontakt mit dem Feed-Server ist ein Problem aufgetreten. Bitte informiere den Eigentümer des Feeds über dieses Problem." +msgstr "Hmm, beim Kontakt mit dem Feed-Server ist ein Problem aufgetreten. Bitte informiere den Eigentümer des Feeds über dieses Problem." #: src/view/com/posts/FeedErrorMessage.tsx:99 msgid "Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue." -msgstr "Hm, der Feed-Server scheint falsch konfiguriert zu sein. Bitte informiere den Eigentümer des Feeds über dieses Problem." +msgstr "Hmm, der Feed-Server scheint falsch konfiguriert zu sein. Bitte informiere den Eigentümer des Feeds über dieses Problem." #: src/view/com/posts/FeedErrorMessage.tsx:105 msgid "Hmm, the feed server appears to be offline. Please let the feed owner know about this issue." -msgstr "Hm, der Feed-Server scheint offline zu sein. Bitte informiere den Eigentümer des Feeds über dieses Problem." +msgstr "Hmm, der Feed-Server scheint offline zu sein. Bitte informiere den Eigentümer des Feeds über dieses Problem." #: src/view/com/posts/FeedErrorMessage.tsx:102 msgid "Hmm, the feed server gave a bad response. Please let the feed owner know about this issue." @@ -1839,7 +1733,7 @@ msgstr "Hmm, der Feed-Server hat eine schlechte Antwort gegeben. Bitte informier #: src/view/com/posts/FeedErrorMessage.tsx:96 msgid "Hmm, we're having trouble finding this feed. It may have been deleted." -msgstr "Hm, wir haben Probleme, diesen Feed zu finden. Möglicherweise wurde er gelöscht." +msgstr "Hmm, wir haben Probleme, diesen Feed zu finden. Möglicherweise wurde er gelöscht." #: src/Navigation.tsx:442 #: src/view/shell/bottom-bar/BottomBar.tsx:137 @@ -1849,13 +1743,6 @@ msgstr "Hm, wir haben Probleme, diesen Feed zu finden. Möglicherweise wurde er msgid "Home" msgstr "Home" -#: src/Navigation.tsx:247 -#: src/view/com/pager/FeedsTabBarMobile.tsx:123 -#: src/view/screens/PreferencesHomeFeed.tsx:104 -#: src/view/screens/Settings/index.tsx:543 -#~ msgid "Home Feed Preferences" -#~ msgstr "Home-Feed-Einstellungen" - #: src/view/com/auth/create/Step1.tsx:75 #: src/view/com/auth/login/ForgotPasswordForm.tsx:120 msgid "Hosting provider" @@ -1863,7 +1750,7 @@ msgstr "Hosting-Anbieter" #: src/view/com/modals/InAppBrowserConsent.tsx:44 msgid "How should we open this link?" -msgstr "" +msgstr "Wie sollen wir diesen Link öffnen?" #: src/view/com/modals/VerifyEmail.tsx:214 msgid "I have a code" @@ -1871,7 +1758,7 @@ msgstr "Ich habe einen Code" #: src/view/com/modals/VerifyEmail.tsx:216 msgid "I have a confirmation code" -msgstr "" +msgstr "Ich habe einen Bestätigungscode" #: src/view/com/modals/ChangeHandle.tsx:283 msgid "I have my own domain" @@ -1879,7 +1766,7 @@ msgstr "Ich habe meine eigene Domain" #: src/view/com/lightbox/Lightbox.web.tsx:165 msgid "If alt text is long, toggles alt text expanded state" -msgstr "" +msgstr "Schaltet den erweiterten Status des Alt-Textes um, wenn dieser lang ist" #: src/view/com/modals/SelfLabel.tsx:127 msgid "If none are selected, suitable for all ages." @@ -1887,11 +1774,11 @@ msgstr "Wenn keine ausgewählt werden, sind sie für alle Altersgruppen geeignet #: src/view/com/modals/ChangePassword.tsx:146 msgid "If you want to change your password, we will send you a code to verify that this is your account." -msgstr "" +msgstr "Wenn du dein Passwort ändern möchtest, senden wir dir einen Code, um zu bestätigen, dass es sich um dein Konto handelt." #: src/view/com/util/images/Gallery.tsx:38 msgid "Image" -msgstr "" +msgstr "Bild" #: src/view/com/modals/AltImage.tsx:120 msgid "Image alt text" @@ -1904,72 +1791,56 @@ msgstr "Bild-Optionen" #: src/view/com/auth/login/SetNewPasswordForm.tsx:138 msgid "Input code sent to your email for password reset" -msgstr "" +msgstr "Gib den Code ein, den du per E-Mail erhalten hast, um dein Passwort zurückzusetzen." #: src/view/com/modals/DeleteAccount.tsx:184 msgid "Input confirmation code for account deletion" -msgstr "" +msgstr "Bestätigungscode für die Kontolöschung eingeben" #: src/view/com/auth/create/Step1.tsx:177 msgid "Input email for Bluesky account" -msgstr "" +msgstr "E-Mail für Bluesky-Konto eingeben" #: src/view/com/auth/create/Step1.tsx:151 msgid "Input invite code to proceed" -msgstr "" +msgstr "Einladungscode eingeben, um fortzufahren" #: src/view/com/modals/AddAppPasswords.tsx:180 msgid "Input name for app password" -msgstr "" +msgstr "Namen für das App-Passwort eingeben" #: src/view/com/auth/login/SetNewPasswordForm.tsx:162 msgid "Input new password" -msgstr "" +msgstr "Neues Passwort eingeben" #: src/view/com/modals/DeleteAccount.tsx:203 msgid "Input password for account deletion" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:196 -#~ msgid "Input phone number for SMS verification" -#~ msgstr "" +msgstr "Passwort für die Kontolöschung eingeben" #: src/view/com/auth/login/LoginForm.tsx:230 msgid "Input the password tied to {identifier}" -msgstr "" +msgstr "Passwort, das an {identifier} gebunden ist, eingeben" #: src/view/com/auth/login/LoginForm.tsx:197 msgid "Input the username or email address you used at signup" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:271 -#~ msgid "Input the verification code we have texted to you" -#~ msgstr "" - -#: src/view/com/modals/Waitlist.tsx:90 -#~ msgid "Input your email to get on the Bluesky waitlist" -#~ msgstr "" +msgstr "Benutzernamen oder E-Mail-Adresse eingeben, die du bei der Anmeldung verwendet hast" #: src/view/com/auth/login/LoginForm.tsx:229 msgid "Input your password" -msgstr "" +msgstr "Gib dein Passwort ein" #: src/view/com/auth/create/Step2.tsx:80 msgid "Input your user handle" -msgstr "" +msgstr "Gib deinen Handle ein" #: src/view/com/post-thread/PostThreadItem.tsx:226 msgid "Invalid or unsupported post record" -msgstr "" +msgstr "Ungültiger oder nicht unterstützter Beitragrekord" #: src/view/com/auth/login/LoginForm.tsx:113 msgid "Invalid username or password" msgstr "Ungültiger Benutzername oder Passwort" -#: src/view/screens/Settings.tsx:411 -#~ msgid "Invite" -#~ msgstr "Einladen" - #: src/view/com/modals/InviteCodes.tsx:93 msgid "Invite a Friend" msgstr "Einen Freund einladen" @@ -1985,41 +1856,24 @@ msgstr "Einladungscode nicht akzeptiert. Überprüfe, ob du ihn richtig eingegeb #: src/view/com/modals/InviteCodes.tsx:170 msgid "Invite codes: {0} available" -msgstr "" - -#: src/view/shell/Drawer.tsx:645 -#~ msgid "Invite codes: {invitesAvailable} available" -#~ msgstr "Einladungscodes: {invitesAvailable} verfügbar" +msgstr "Einladungscodes: {0} verfügbar" #: src/view/com/modals/InviteCodes.tsx:169 msgid "Invite codes: 1 available" -msgstr "" +msgstr "Einladungscodes: 1 verfügbar" #: src/screens/Onboarding/StepFollowingFeed.tsx:64 msgid "It shows posts from the people you follow as they happen." -msgstr "" +msgstr "Es zeigt die Beiträge der Personen an, denen du folgst, sobald sie erscheinen." #: src/view/com/auth/HomeLoggedOutCTA.tsx:99 #: src/view/com/auth/SplashScreen.web.tsx:138 msgid "Jobs" msgstr "Jobs" -#: src/view/com/modals/Waitlist.tsx:67 -#~ msgid "Join the waitlist" -#~ msgstr "Der Warteliste beitreten" - -#: src/view/com/auth/create/Step1.tsx:174 -#: src/view/com/auth/create/Step1.tsx:178 -#~ msgid "Join the waitlist." -#~ msgstr "Der Warteliste beitreten." - -#: src/view/com/modals/Waitlist.tsx:128 -#~ msgid "Join Waitlist" -#~ msgstr "Warteliste beitreten" - #: src/screens/Onboarding/index.tsx:24 msgid "Journalism" -msgstr "" +msgstr "Journalismus" #: src/view/com/composer/select-language/SelectLangBtn.tsx:104 msgid "Language selection" @@ -2027,7 +1881,7 @@ msgstr "Sprachauswahl" #: src/view/screens/Settings/index.tsx:594 msgid "Language settings" -msgstr "" +msgstr "Spracheinstellungen" #: src/Navigation.tsx:142 #: src/view/screens/LanguageSettings.tsx:89 @@ -2040,7 +1894,7 @@ msgstr "Sprachen" #: src/view/com/auth/create/StepHeader.tsx:20 msgid "Last step!" -msgstr "" +msgstr "Letzter Schritt!" #: src/view/com/util/moderation/ContentHider.tsx:103 msgid "Learn more" @@ -2074,11 +1928,11 @@ msgstr "Bluesky verlassen" #: src/screens/Deactivated.tsx:128 msgid "left to go." -msgstr "" +msgstr "noch übrig." #: src/view/screens/Settings/index.tsx:278 msgid "Legacy storage cleared, you need to restart the app now." -msgstr "" +msgstr "Der Legacy-Speicher wurde gelöscht, du musst die App jetzt neu starten." #: src/view/com/auth/login/Login.tsx:128 #: src/view/com/auth/login/Login.tsx:144 @@ -2087,7 +1941,7 @@ msgstr "Lass uns dein Passwort zurücksetzen!" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Let's go!" -msgstr "" +msgstr "Los geht's!" #: src/view/com/util/UserAvatar.tsx:248 #: src/view/com/util/UserBanner.tsx:62 @@ -2096,84 +1950,84 @@ msgstr "Bibliothek" #: src/view/screens/Settings/index.tsx:479 msgid "Light" -msgstr "" +msgstr "Licht" #: src/view/com/util/post-ctrls/PostCtrls.tsx:182 msgid "Like" -msgstr "" +msgstr "Liken" #: src/view/screens/ProfileFeed.tsx:591 msgid "Like this feed" -msgstr "" +msgstr "Diesen Feed liken" #: src/Navigation.tsx:199 msgid "Liked by" -msgstr "" +msgstr "Gelikt von" #: src/view/screens/PostLikedBy.tsx:27 #: src/view/screens/ProfileFeedLikedBy.tsx:27 msgid "Liked By" -msgstr "" +msgstr "Gelikt von" #: src/view/com/feeds/FeedSourceCard.tsx:279 msgid "Liked by {0} {1}" -msgstr "" +msgstr "Von {0} {1} gelikt" #: src/view/screens/ProfileFeed.tsx:606 msgid "Liked by {likeCount} {0}" -msgstr "" +msgstr "Von {likeCount} {0} gelikt" #: src/view/com/notifications/FeedItem.tsx:170 msgid "liked your custom feed" -msgstr "" +msgstr "hat deinen benutzerdefinierten Feed gelikt" #: src/view/com/notifications/FeedItem.tsx:155 msgid "liked your post" -msgstr "" +msgstr "hat deinen Beitrag gelikt" #: src/view/screens/Profile.tsx:183 msgid "Likes" -msgstr "" +msgstr "Likes" #: src/view/com/post-thread/PostThreadItem.tsx:183 msgid "Likes on this post" -msgstr "" +msgstr "Likes für diesen Beitrag" #: src/Navigation.tsx:168 msgid "List" -msgstr "" +msgstr "Liste" #: src/view/com/modals/CreateOrEditList.tsx:261 msgid "List Avatar" -msgstr "" +msgstr "Avatar auflisten" #: src/view/screens/ProfileList.tsx:324 msgid "List blocked" -msgstr "" +msgstr "Liste blockiert" #: src/view/com/feeds/FeedSourceCard.tsx:233 msgid "List by {0}" -msgstr "" +msgstr "Liste von {0}" #: src/view/screens/ProfileList.tsx:378 msgid "List deleted" -msgstr "" +msgstr "Liste gelöscht" #: src/view/screens/ProfileList.tsx:283 msgid "List muted" -msgstr "" +msgstr "Liste stummgeschaltet" #: src/view/com/modals/CreateOrEditList.tsx:275 msgid "List Name" -msgstr "" +msgstr "Name der Liste" #: src/view/screens/ProfileList.tsx:343 msgid "List unblocked" -msgstr "" +msgstr "Liste entblockiert" #: src/view/screens/ProfileList.tsx:302 msgid "List unmuted" -msgstr "" +msgstr "Listenstummschaltung aufgehoben" #: src/Navigation.tsx:112 #: src/view/screens/Profile.tsx:185 @@ -2190,7 +2044,7 @@ msgstr "Mehr Beiträge laden" #: src/view/screens/Notifications.tsx:159 msgid "Load new notifications" -msgstr "Neue Benachrichtigungen laden" +msgstr "Neue Mitteilungen laden" #: src/view/com/feeds/FeedPage.tsx:115 #: src/view/screens/Profile.tsx:440 @@ -2203,24 +2057,20 @@ msgstr "Neue Beiträge laden" msgid "Loading..." msgstr "Wird geladen..." -#: src/view/com/modals/ServerInput.tsx:50 -#~ msgid "Local dev server" -#~ msgstr "Lokaler Entwicklungsserver" - #: src/Navigation.tsx:209 msgid "Log" -msgstr "" +msgstr "Systemprotokoll" #: src/screens/Deactivated.tsx:149 #: src/screens/Deactivated.tsx:152 #: src/screens/Deactivated.tsx:178 #: src/screens/Deactivated.tsx:181 msgid "Log out" -msgstr "" +msgstr "Abmelden" #: src/view/screens/Moderation.tsx:155 msgid "Logged-out visibility" -msgstr "" +msgstr "Sichtbarkeit für abgemeldete Benutzer" #: src/view/com/auth/login/ChooseAccountForm.tsx:133 msgid "Login to account that is not listed" @@ -2232,15 +2082,15 @@ msgstr "Vergewissere dich, dass du auch wirklich dorthin gehen willst!" #: src/components/dialogs/MutedWords.tsx:83 msgid "Manage your muted words and tags" -msgstr "" +msgstr "Verwalte deine stummgeschalteten Wörter und Tags" #: src/view/com/auth/create/Step2.tsx:118 msgid "May not be longer than 253 characters" -msgstr "" +msgstr "Darf nicht länger als 253 Zeichen sein" #: src/view/com/auth/create/Step2.tsx:109 msgid "May only contain letters and numbers" -msgstr "" +msgstr "Darf nur Buchstaben und Zahlen enthalten" #: src/view/screens/Profile.tsx:182 msgid "Media" @@ -2275,25 +2125,25 @@ msgstr "Moderation" #: src/view/com/lists/ListCard.tsx:93 #: src/view/com/modals/UserAddRemoveLists.tsx:206 msgid "Moderation list by {0}" -msgstr "" +msgstr "Moderationsliste von {0}" #: src/view/screens/ProfileList.tsx:775 msgid "Moderation list by <0/>" -msgstr "" +msgstr "Moderationsliste von <0/>" #: src/view/com/lists/ListCard.tsx:91 #: src/view/com/modals/UserAddRemoveLists.tsx:204 #: src/view/screens/ProfileList.tsx:773 msgid "Moderation list by you" -msgstr "" +msgstr "Moderationsliste von dir" #: src/view/com/modals/CreateOrEditList.tsx:197 msgid "Moderation list created" -msgstr "" +msgstr "Moderationsliste erstellt" #: src/view/com/modals/CreateOrEditList.tsx:183 msgid "Moderation list updated" -msgstr "" +msgstr "Moderationsliste aktualisiert" #: src/view/screens/Moderation.tsx:114 msgid "Moderation lists" @@ -2306,11 +2156,11 @@ msgstr "Moderationslisten" #: src/view/screens/Settings/index.tsx:619 msgid "Moderation settings" -msgstr "" +msgstr "Moderationseinstellungen" #: src/view/com/modals/ModerationDetails.tsx:35 msgid "Moderator has chosen to set a general warning on the content." -msgstr "" +msgstr "Der Moderator hat beschlossen, eine allgemeine Warnung vor dem Inhalt auszusprechen." #: src/view/shell/desktop/Feeds.tsx:65 msgid "More feeds" @@ -2322,25 +2172,21 @@ msgstr "Mehr Feeds" msgid "More options" msgstr "Mehr Optionen" -#: src/view/com/util/forms/PostDropdownBtn.tsx:315 -#~ msgid "More post options" -#~ msgstr "" - #: src/view/screens/PreferencesThreads.tsx:82 msgid "Most-liked replies first" -msgstr "" +msgstr "Beliebteste Antworten zuerst" #: src/view/com/auth/create/Step2.tsx:122 msgid "Must be at least 3 characters" -msgstr "" +msgstr "Muss mindestens 3 Zeichen lang sein" #: src/components/TagMenu/index.tsx:249 msgid "Mute" -msgstr "" +msgstr "Stummschalten" #: src/components/TagMenu/index.web.tsx:105 msgid "Mute {truncatedTag}" -msgstr "" +msgstr "{truncatedTag} stummschalten" #: src/view/com/profile/ProfileHeader.tsx:327 msgid "Mute Account" @@ -2352,23 +2198,19 @@ msgstr "Konten stummschalten" #: src/components/TagMenu/index.tsx:209 msgid "Mute all {displayTag} posts" -msgstr "" - -#: src/components/TagMenu/index.tsx:211 -#~ msgid "Mute all {tag} posts" -#~ msgstr "" +msgstr "Alle {displayTag}-Beiträge stummschalten" #: src/components/dialogs/MutedWords.tsx:149 msgid "Mute in tags only" -msgstr "" +msgstr "Nur in Tags stummschalten" #: src/components/dialogs/MutedWords.tsx:134 msgid "Mute in text & tags" -msgstr "" +msgstr "In Text und Tags stummschalten" #: src/view/screens/ProfileList.tsx:491 msgid "Mute list" -msgstr "" +msgstr "Liste stummschalten" #: src/view/screens/ProfileList.tsx:275 msgid "Mute these accounts?" @@ -2376,15 +2218,15 @@ msgstr "Diese Konten stummschalten?" #: src/view/screens/ProfileList.tsx:279 msgid "Mute this List" -msgstr "" +msgstr "Diese Liste stummschalten" #: src/components/dialogs/MutedWords.tsx:127 msgid "Mute this word in post text and tags" -msgstr "" +msgstr "Dieses Wort in Beitragstexten und Tags stummschalten" #: src/components/dialogs/MutedWords.tsx:142 msgid "Mute this word in tags only" -msgstr "" +msgstr "Dieses Wort nur in Tags stummschalten" #: src/view/com/util/forms/PostDropdownBtn.tsx:251 #: src/view/com/util/forms/PostDropdownBtn.tsx:257 @@ -2394,11 +2236,11 @@ msgstr "Thread stummschalten" #: src/view/com/util/forms/PostDropdownBtn.tsx:267 #: src/view/com/util/forms/PostDropdownBtn.tsx:269 msgid "Mute words & tags" -msgstr "" +msgstr "Wörter und Tags stummschalten" #: src/view/com/lists/ListCard.tsx:102 msgid "Muted" -msgstr "" +msgstr "Stummgeschaltet" #: src/view/screens/Moderation.tsx:128 msgid "Muted accounts" @@ -2411,15 +2253,15 @@ msgstr "Stummgeschaltete Konten" #: src/view/screens/ModerationMutedAccounts.tsx:115 msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private." -msgstr "Bei stummgeschalteten Konten werden ihre Beiträge aus deinem Feed und deinen Benachrichtigungen entfernt. Stummschaltungen sind völlig privat." +msgstr "Bei stummgeschalteten Konten werden dazugehörige Beiträge aus deinem Feed und deinen Mitteilungen entfernt. Stummschaltungen sind völlig privat." #: src/view/screens/Moderation.tsx:100 msgid "Muted words & tags" -msgstr "" +msgstr "Stummgeschaltete Wörter und Tags" #: src/view/screens/ProfileList.tsx:277 msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." -msgstr "Stummschaltung ist privat. Stummgeschaltete Konten können mit dir interagieren, aber du siehst ihre Beiträge nicht und erhältst keine Benachrichtigungen von ihnen." +msgstr "Stummschaltung ist privat. Stummgeschaltete Konten können mit dir interagieren, aber du siehst ihre Beiträge nicht und erhältst keine Mitteilungen von ihnen." #: src/view/com/modals/BirthDateSettings.tsx:56 msgid "My Birthday" @@ -2439,7 +2281,7 @@ msgstr "Meine gespeicherten Feeds" #: src/view/com/auth/server-input/index.tsx:118 msgid "my-server.com" -msgstr "" +msgstr "mein-server.de" #: src/view/com/modals/AddAppPasswords.tsx:179 #: src/view/com/modals/CreateOrEditList.tsx:290 @@ -2448,11 +2290,11 @@ msgstr "Name" #: src/view/com/modals/CreateOrEditList.tsx:145 msgid "Name is required" -msgstr "" +msgstr "Name ist erforderlich" #: src/screens/Onboarding/index.tsx:25 msgid "Nature" -msgstr "" +msgstr "Natur" #: src/view/com/auth/login/ForgotPasswordForm.tsx:190 #: src/view/com/auth/login/ForgotPasswordForm.tsx:219 @@ -2460,16 +2302,16 @@ msgstr "" #: src/view/com/auth/login/SetNewPasswordForm.tsx:196 #: src/view/com/modals/ChangePassword.tsx:166 msgid "Navigates to the next screen" -msgstr "" +msgstr "Navigiert zum nächsten Bildschirm" #: src/view/shell/Drawer.tsx:71 msgid "Navigates to your profile" -msgstr "" +msgstr "Navigiert zu Deinem Profil" #: src/view/com/modals/EmbedConsent.tsx:107 #: src/view/com/modals/EmbedConsent.tsx:123 msgid "Never load embeds from {0}" -msgstr "" +msgstr "Lade niemals eingebettete Medien von {0}" #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:72 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:72 @@ -2478,16 +2320,16 @@ msgstr "Verliere nie den Zugriff auf deine Follower und Daten." #: src/screens/Onboarding/StepFinished.tsx:119 msgid "Never lose access to your followers or data." -msgstr "" +msgstr "Verliere nie den Zugriff auf deine Follower oder Daten." #: src/components/dialogs/MutedWords.tsx:293 msgid "Nevermind" -msgstr "" +msgstr "Egal" #: src/view/screens/Lists.tsx:76 msgctxt "action" msgid "New" -msgstr "" +msgstr "Neu" #: src/view/screens/ModerationModlists.tsx:78 msgid "New" @@ -2495,20 +2337,20 @@ msgstr "Neu" #: src/view/com/modals/CreateOrEditList.tsx:252 msgid "New Moderation List" -msgstr "" +msgstr "Neue Moderationsliste" #: src/view/com/auth/login/SetNewPasswordForm.tsx:150 msgid "New password" -msgstr "" +msgstr "Neues Passwort" #: src/view/com/modals/ChangePassword.tsx:215 msgid "New Password" -msgstr "" +msgstr "Neues Passwort" #: src/view/com/feeds/FeedPage.tsx:126 msgctxt "action" msgid "New post" -msgstr "" +msgstr "Neuer Beitrag" #: src/view/screens/Feeds.tsx:555 #: src/view/screens/Notifications.tsx:168 @@ -2527,15 +2369,15 @@ msgstr "Neuer Beitrag" #: src/view/com/modals/CreateOrEditList.tsx:247 msgid "New User List" -msgstr "" +msgstr "Neue Benutzerliste" #: src/view/screens/PreferencesThreads.tsx:79 msgid "Newest replies first" -msgstr "" +msgstr "Neueste Antworten zuerst" #: src/screens/Onboarding/index.tsx:23 msgid "News" -msgstr "" +msgstr "Aktuelles" #: src/view/com/auth/create/CreateAccount.tsx:172 #: src/view/com/auth/login/ForgotPasswordForm.tsx:182 @@ -2552,7 +2394,7 @@ msgstr "Nächste" #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103 msgctxt "action" msgid "Next" -msgstr "" +msgstr "Nächste" #: src/view/com/lightbox/Lightbox.web.tsx:149 msgid "Next image" @@ -2574,11 +2416,11 @@ msgstr "Keine Beschreibung" #: src/view/com/profile/ProfileHeader.tsx:170 msgid "No longer following {0}" -msgstr "" +msgstr "{0} wird nicht mehr gefolgt" #: src/view/com/notifications/Feed.tsx:109 msgid "No notifications yet!" -msgstr "" +msgstr "Noch keine Mitteilungen!" #: src/view/com/composer/text-input/mobile/Autocomplete.tsx:97 #: src/view/com/composer/text-input/web/Autocomplete.tsx:191 @@ -2587,7 +2429,7 @@ msgstr "Kein Ergebnis" #: src/components/Lists.tsx:192 msgid "No results found" -msgstr "" +msgstr "Keine Ergebnisse gefunden" #: src/view/screens/Feeds.tsx:495 msgid "No results found for \"{query}\"" @@ -2601,7 +2443,7 @@ msgstr "Keine Ergebnisse für {query} gefunden" #: src/view/com/modals/EmbedConsent.tsx:129 msgid "No thanks" -msgstr "" +msgstr "Nein danke" #: src/view/com/modals/Threadgate.tsx:82 msgid "Nobody" @@ -2614,12 +2456,12 @@ msgstr "Unzutreffend." #: src/Navigation.tsx:107 #: src/view/screens/Profile.tsx:106 msgid "Not Found" -msgstr "" +msgstr "Nicht gefunden" #: src/view/com/modals/VerifyEmail.tsx:246 #: src/view/com/modals/VerifyEmail.tsx:252 msgid "Not right now" -msgstr "" +msgstr "Im Moment nicht" #: src/view/screens/Moderation.tsx:252 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." @@ -2637,7 +2479,7 @@ msgstr "Mitteilungen" #: src/view/com/modals/SelfLabel.tsx:103 msgid "Nudity" -msgstr "" +msgstr "Nacktheit" #: src/view/com/util/ErrorBoundary.tsx:35 msgid "Oh no!" @@ -2645,7 +2487,7 @@ msgstr "Oh nein!" #: src/screens/Onboarding/StepInterests/index.tsx:128 msgid "Oh no! Something went wrong." -msgstr "" +msgstr "Oh nein, da ist etwas schief gelaufen." #: src/view/com/auth/login/PasswordUpdatedForm.tsx:41 msgid "Okay" @@ -2653,11 +2495,11 @@ msgstr "Okay" #: src/view/screens/PreferencesThreads.tsx:78 msgid "Oldest replies first" -msgstr "" +msgstr "Älteste Antworten zuerst" #: src/view/screens/Settings/index.tsx:234 msgid "Onboarding reset" -msgstr "" +msgstr "Onboarding zurücksetzen" #: src/view/com/composer/Composer.tsx:382 msgid "One or more images is missing alt text." @@ -2669,34 +2511,34 @@ msgstr "Nur {0} kann antworten." #: src/components/Lists.tsx:82 msgid "Oops, something went wrong!" -msgstr "" +msgstr "Ups, da ist etwas schief gelaufen!" #: src/components/Lists.tsx:188 #: src/view/screens/AppPasswords.tsx:65 #: src/view/screens/Profile.tsx:106 msgid "Oops!" -msgstr "" +msgstr "Huch!" #: src/screens/Onboarding/StepFinished.tsx:115 msgid "Open" -msgstr "" +msgstr "Öffnen" #: src/view/screens/Moderation.tsx:75 msgid "Open content filtering settings" -msgstr "" +msgstr "Inhaltsfiltereinstellungen öffnen" #: src/view/com/composer/Composer.tsx:477 #: src/view/com/composer/Composer.tsx:478 msgid "Open emoji picker" -msgstr "" +msgstr "Emoji-Picker öffnen" #: src/view/screens/Settings/index.tsx:712 msgid "Open links with in-app browser" -msgstr "" +msgstr "Links mit In-App-Browser öffnen" #: src/view/screens/Moderation.tsx:92 msgid "Open muted words settings" -msgstr "" +msgstr "Einstellungen für stummgeschaltete Wörter öffnen" #: src/view/com/home/HomeHeaderLayoutMobile.tsx:50 msgid "Open navigation" @@ -2704,31 +2546,31 @@ msgstr "Navigation öffnen" #: src/view/com/util/forms/PostDropdownBtn.tsx:175 msgid "Open post options menu" -msgstr "" +msgstr "Beitragsoptionsmenü öffnen" #: src/view/screens/Settings/index.tsx:804 msgid "Open storybook page" -msgstr "" +msgstr "Geschichtenbuch öffnen" #: src/view/com/util/forms/DropdownButton.tsx:154 msgid "Opens {numItems} options" -msgstr "" +msgstr "Öffnet {numItems} Optionen" #: src/view/screens/Log.tsx:54 msgid "Opens additional details for a debug entry" -msgstr "" +msgstr "Öffnet zusätzliche Details für einen Debug-Eintrag" #: src/view/com/notifications/FeedItem.tsx:349 msgid "Opens an expanded list of users in this notification" -msgstr "" +msgstr "Öffnet eine erweiterte Liste der Benutzer in dieser Meldung" #: src/view/com/composer/photos/OpenCameraBtn.tsx:61 msgid "Opens camera on device" -msgstr "" +msgstr "Öffnet die Kamera auf dem Gerät" #: src/view/com/composer/Prompt.tsx:25 msgid "Opens composer" -msgstr "" +msgstr "Öffnet den Beitragsverfasser" #: src/view/screens/Settings/index.tsx:595 msgid "Opens configurable language settings" @@ -2736,27 +2578,23 @@ msgstr "Öffnet die konfigurierbaren Spracheinstellungen" #: src/view/com/composer/photos/SelectPhotoBtn.tsx:44 msgid "Opens device photo gallery" -msgstr "" +msgstr "Öffnet die Gerätefotogalerie" #: src/view/com/profile/ProfileHeader.tsx:420 msgid "Opens editor for profile display name, avatar, background image, and description" -msgstr "" +msgstr "Öffnet den Editor für Profilanzeige, Avatar, Hintergrundbild und Beschreibung" #: src/view/screens/Settings/index.tsx:649 msgid "Opens external embeds settings" -msgstr "" +msgstr "Öffnet die Einstellungen für externe eingebettete Medien" #: src/view/com/profile/ProfileHeader.tsx:575 msgid "Opens followers list" -msgstr "" +msgstr "Öffnet die Follower-Liste" #: src/view/com/profile/ProfileHeader.tsx:594 msgid "Opens following list" -msgstr "" - -#: src/view/screens/Settings.tsx:412 -#~ msgid "Opens invite code list" -#~ msgstr "" +msgstr "Öffnet folgende Liste" #: src/view/com/modals/InviteCodes.tsx:172 msgid "Opens list of invite codes" @@ -2764,7 +2602,7 @@ msgstr "Öffnet die Liste der Einladungscodes" #: src/view/screens/Settings/index.tsx:774 msgid "Opens modal for account deletion confirmation. Requires email code." -msgstr "" +msgstr "Öffnet ein Modal, um die Löschung des Kontos zu bestätigen. Erfordert einen E-Mail-Code." #: src/view/com/modals/ChangeHandle.tsx:281 msgid "Opens modal for using custom domain" @@ -2776,12 +2614,12 @@ msgstr "Öffnet die Moderationseinstellungen" #: src/view/com/auth/login/LoginForm.tsx:239 msgid "Opens password reset form" -msgstr "" +msgstr "Öffnet das Formular zum Zurücksetzen des Passworts" #: src/view/com/home/HomeHeaderLayout.web.tsx:63 #: src/view/screens/Feeds.tsx:356 msgid "Opens screen to edit Saved Feeds" -msgstr "" +msgstr "Öffnet den Bildschirm zum Bearbeiten gespeicherten Feeds" #: src/view/screens/Settings/index.tsx:576 msgid "Opens screen with all saved feeds" @@ -2797,7 +2635,7 @@ msgstr "Öffnet die Home-Feed-Einstellungen" #: src/view/screens/Settings/index.tsx:805 msgid "Opens the storybook page" -msgstr "" +msgstr "Öffnet die Geschichtenbuch" #: src/view/screens/Settings/index.tsx:793 msgid "Opens the system log page" @@ -2809,20 +2647,16 @@ msgstr "Öffnet die Thread-Einstellungen" #: src/view/com/util/forms/DropdownButton.tsx:280 msgid "Option {0} of {numItems}" -msgstr "" +msgstr "Option {0} von {numItems}" #: src/view/com/modals/Threadgate.tsx:89 msgid "Or combine these options:" -msgstr "" +msgstr "Oder kombiniere diese Optionen:" #: src/view/com/auth/login/ChooseAccountForm.tsx:138 msgid "Other account" msgstr "Anderes Konto" -#: src/view/com/modals/ServerInput.tsx:88 -#~ msgid "Other service" -#~ msgstr "Anderer Service" - #: src/view/com/composer/select-language/SelectLangBtn.tsx:91 msgid "Other..." msgstr "Andere..." @@ -2834,7 +2668,7 @@ msgstr "Seite nicht gefunden" #: src/view/screens/NotFound.tsx:42 msgid "Page Not Found" -msgstr "" +msgstr "Seite nicht gefunden" #: src/view/com/auth/create/Step1.tsx:191 #: src/view/com/auth/create/Step1.tsx:201 @@ -2854,27 +2688,23 @@ msgstr "Passwort aktualisiert!" #: src/Navigation.tsx:162 msgid "People followed by @{0}" -msgstr "" +msgstr "Personen gefolgt von @{0}" #: src/Navigation.tsx:155 msgid "People following @{0}" -msgstr "" +msgstr "Personen, die @{0} folgen" #: src/view/com/lightbox/Lightbox.tsx:66 msgid "Permission to access camera roll is required." -msgstr "" +msgstr "Die Erlaubnis zum Zugriff auf die Kamerarolle ist erforderlich." #: src/view/com/lightbox/Lightbox.tsx:72 msgid "Permission to access camera roll was denied. Please enable it in your system settings." -msgstr "" +msgstr "Die Berechtigung zum Zugriff auf die Kamerarolle wurde verweigert. Bitte aktiviere sie in deinen Systemeinstellungen." #: src/screens/Onboarding/index.tsx:31 msgid "Pets" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:183 -#~ msgid "Phone number" -#~ msgstr "" +msgstr "Haustiere" #: src/view/com/modals/SelfLabel.tsx:121 msgid "Pictures meant for adults." @@ -2883,7 +2713,7 @@ msgstr "Bilder, die für Erwachsene bestimmt sind." #: src/view/screens/ProfileFeed.tsx:354 #: src/view/screens/ProfileList.tsx:581 msgid "Pin to home" -msgstr "" +msgstr "An die Startseite anheften" #: src/view/screens/SavedFeeds.tsx:88 msgid "Pinned Feeds" @@ -2891,16 +2721,16 @@ msgstr "Angeheftete Feeds" #: src/view/com/util/post-embeds/ExternalGifEmbed.tsx:111 msgid "Play {0}" -msgstr "" +msgstr "{0} abspielen" #: src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx:54 #: src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx:55 msgid "Play Video" -msgstr "" +msgstr "Video abspielen" #: src/view/com/util/post-embeds/ExternalGifEmbed.tsx:110 msgid "Plays the GIF" -msgstr "" +msgstr "Spielt das GIF ab" #: src/view/com/auth/create/state.ts:124 msgid "Please choose your handle." @@ -2912,7 +2742,7 @@ msgstr "Bitte wähle dein Passwort." #: src/view/com/auth/create/state.ts:131 msgid "Please complete the verification captcha." -msgstr "" +msgstr "Bitte fülle das Verifizierungs-Captcha aus." #: src/view/com/modals/ChangeEmail.tsx:67 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." @@ -2920,27 +2750,15 @@ msgstr "Bitte bestätige deine E-Mail, bevor du sie änderst. Dies ist eine vorà #: src/view/com/modals/AddAppPasswords.tsx:90 msgid "Please enter a name for your app password. All spaces is not allowed." -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:206 -#~ msgid "Please enter a phone number that can receive SMS text messages." -#~ msgstr "" +msgstr "Bitte gib einen Namen für dein App-Passwort ein. Nur Leerzeichen sind nicht erlaubt." #: src/view/com/modals/AddAppPasswords.tsx:145 msgid "Please enter a unique name for this App Password or use our randomly generated one." -msgstr "" +msgstr "Bitte gib einen eindeutigen Namen für dieses App-Passwort ein oder verwende unseren zufällig generierten Namen." #: src/components/dialogs/MutedWords.tsx:68 msgid "Please enter a valid word, tag, or phrase to mute" -msgstr "" - -#: src/view/com/auth/create/state.ts:170 -#~ msgid "Please enter the code you received by SMS." -#~ msgstr "" - -#: src/view/com/auth/create/Step2.tsx:282 -#~ msgid "Please enter the verification code sent to {phoneNumberFormatted}." -#~ msgstr "" +msgstr "Bitte gib ein gültiges Wort, einen Tag oder eine Phrase zum Stummschalten ein" #: src/view/com/auth/create/state.ts:103 msgid "Please enter your email." @@ -2957,25 +2775,25 @@ msgstr "Bitte teile uns mit, warum du denkst, dass diese Inhaltswarnung falsch a #: src/view/com/modals/VerifyEmail.tsx:101 msgid "Please Verify Your Email" -msgstr "" +msgstr "Bitte verifiziere deine E-Mail" #: src/view/com/composer/Composer.tsx:222 msgid "Please wait for your link card to finish loading" -msgstr "" +msgstr "Bitte warte, bis deine Link-karte vollständig geladen ist" #: src/screens/Onboarding/index.tsx:37 msgid "Politics" -msgstr "" +msgstr "Politik" #: src/view/com/modals/SelfLabel.tsx:111 msgid "Porn" -msgstr "" +msgstr "Porno" #: src/view/com/composer/Composer.tsx:357 #: src/view/com/composer/Composer.tsx:365 msgctxt "action" msgid "Post" -msgstr "" +msgstr "Beitrag" #: src/view/com/post-thread/PostThread.tsx:303 msgctxt "description" @@ -2984,17 +2802,17 @@ msgstr "Beitrag" #: src/view/com/post-thread/PostThreadItem.tsx:175 msgid "Post by {0}" -msgstr "" +msgstr "Beitrag von {0}" #: src/Navigation.tsx:174 #: src/Navigation.tsx:181 #: src/Navigation.tsx:188 msgid "Post by @{0}" -msgstr "" +msgstr "Beitrag von @{0}" #: src/view/com/util/forms/PostDropdownBtn.tsx:108 msgid "Post deleted" -msgstr "" +msgstr "Beitrag gelöscht" #: src/view/com/post-thread/PostThread.tsx:462 msgid "Post hidden" @@ -3014,7 +2832,7 @@ msgstr "Beitrag nicht gefunden" #: src/components/TagMenu/index.tsx:253 msgid "posts" -msgstr "" +msgstr "Beiträge" #: src/view/screens/Profile.tsx:180 msgid "Posts" @@ -3022,11 +2840,11 @@ msgstr "Beiträge" #: src/components/dialogs/MutedWords.tsx:90 msgid "Posts can be muted based on their text, their tags, or both." -msgstr "" +msgstr "Beiträge können basierend auf ihrem Text, ihren Tags oder beidem stummgeschaltet werden." #: src/view/com/posts/FeedErrorMessage.tsx:64 msgid "Posts hidden" -msgstr "" +msgstr "Ausgeblendete Beiträge" #: src/view/com/modals/LinkWarning.tsx:46 msgid "Potentially Misleading Link" @@ -3070,7 +2888,7 @@ msgstr "Profil" #: src/view/com/modals/EditProfile.tsx:128 msgid "Profile updated" -msgstr "" +msgstr "Profil aktualisiert" #: src/view/screens/Settings/index.tsx:949 msgid "Protect your account by verifying your email." @@ -3078,7 +2896,7 @@ msgstr "Schütze dein Konto, indem du deine E-Mail bestätigst." #: src/screens/Onboarding/StepFinished.tsx:101 msgid "Public" -msgstr "" +msgstr "Öffentlich" #: src/view/screens/ModerationModlists.tsx:61 msgid "Public, shareable lists of users to mute or block in bulk." @@ -3090,11 +2908,11 @@ msgstr "Öffentliche, gemeinsam nutzbare Listen, die Feeds steuern können." #: src/view/com/composer/Composer.tsx:342 msgid "Publish post" -msgstr "" +msgstr "Beitrag veröffentlichen" #: src/view/com/composer/Composer.tsx:342 msgid "Publish reply" -msgstr "" +msgstr "Antwort veröffentlichen" #: src/view/com/modals/Repost.tsx:65 msgctxt "action" @@ -3112,11 +2930,11 @@ msgstr "Beitrag zitieren" #: src/view/screens/PreferencesThreads.tsx:86 msgid "Random (aka \"Poster's Roulette\")" -msgstr "" +msgstr "Zufällig (alias \"Poster's Roulette\")" #: src/view/com/modals/EditImage.tsx:236 msgid "Ratios" -msgstr "" +msgstr "Verhältnisse" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:116 msgid "Recommended Feeds" @@ -3166,11 +2984,11 @@ msgstr "Bildvorschau entfernen" #: src/components/dialogs/MutedWords.tsx:343 msgid "Remove mute word from your list" -msgstr "" +msgstr "Stummgeschaltetes Wort aus deiner Liste entfernen" #: src/view/com/modals/Repost.tsx:47 msgid "Remove repost" -msgstr "" +msgstr "Repost entfernen" #: src/view/com/feeds/FeedSourceCard.tsx:175 msgid "Remove this feed from my feeds?" @@ -3188,11 +3006,11 @@ msgstr "Aus der Liste entfernt" #: src/view/com/feeds/FeedSourceCard.tsx:113 #: src/view/com/feeds/FeedSourceCard.tsx:180 msgid "Removed from my feeds" -msgstr "" +msgstr "Aus meinen Feeds entfernt" #: src/view/com/composer/ExternalEmbed.tsx:71 msgid "Removes default thumbnail from {0}" -msgstr "" +msgstr "Entfernt Standard-Miniaturansicht von {0}" #: src/view/screens/Profile.tsx:181 msgid "Replies" @@ -3205,7 +3023,7 @@ msgstr "Antworten auf diesen Thread sind deaktiviert" #: src/view/com/composer/Composer.tsx:355 msgctxt "action" msgid "Reply" -msgstr "" +msgstr "Antworten" #: src/view/screens/PreferencesFollowingFeed.tsx:144 msgid "Reply Filters" @@ -3215,7 +3033,7 @@ msgstr "Antwortfilter" #: src/view/com/posts/FeedItem.tsx:287 msgctxt "description" msgid "Reply to <0/>" -msgstr "" +msgstr "Antwort an <0/>" #: src/view/com/modals/report/Modal.tsx:166 msgid "Report {collectionName}" @@ -3245,7 +3063,7 @@ msgstr "Beitrag melden" #: src/view/com/util/post-ctrls/RepostButton.tsx:61 msgctxt "action" msgid "Repost" -msgstr "" +msgstr "Repost" #: src/view/com/util/post-ctrls/RepostButton.web.tsx:48 msgid "Repost" @@ -3254,45 +3072,41 @@ msgstr "Erneut veröffentlichen" #: src/view/com/util/post-ctrls/RepostButton.web.tsx:94 #: src/view/com/util/post-ctrls/RepostButton.web.tsx:105 msgid "Repost or quote post" -msgstr "" +msgstr "Reposten oder Beitrag zitieren" #: src/view/screens/PostRepostedBy.tsx:27 msgid "Reposted By" -msgstr "" +msgstr "Repostet von" #: src/view/com/posts/FeedItem.tsx:207 msgid "Reposted by {0}" -msgstr "" +msgstr "Repostet von {0}" #: src/view/com/posts/FeedItem.tsx:224 msgid "Reposted by <0/>" -msgstr "" +msgstr "Repostet von <0/>" #: src/view/com/notifications/FeedItem.tsx:162 msgid "reposted your post" -msgstr "" +msgstr "hat deinen Beitrag repostet" #: src/view/com/post-thread/PostThreadItem.tsx:188 msgid "Reposts of this post" -msgstr "" +msgstr "Reposts von diesem Beitrag" #: src/view/com/modals/ChangeEmail.tsx:181 #: src/view/com/modals/ChangeEmail.tsx:183 msgid "Request Change" msgstr "Änderung anfordern" -#: src/view/com/auth/create/Step2.tsx:219 -#~ msgid "Request code" -#~ msgstr "" - #: src/view/com/modals/ChangePassword.tsx:239 #: src/view/com/modals/ChangePassword.tsx:241 msgid "Request Code" -msgstr "" +msgstr "Einen Code anfordern" #: src/view/screens/Settings/index.tsx:456 msgid "Require alt text before posting" -msgstr "" +msgstr "Alt-Text vor der Veröffentlichung erforderlich machen" #: src/view/com/auth/create/Step1.tsx:146 msgid "Required for this provider" @@ -3305,15 +3119,15 @@ msgstr "Code zurücksetzen" #: src/view/com/modals/ChangePassword.tsx:190 msgid "Reset Code" -msgstr "" +msgstr "Code zurücksetzen" #: src/view/screens/Settings/index.tsx:824 msgid "Reset onboarding" -msgstr "" +msgstr "Onboarding zurücksetzen" #: src/view/screens/Settings/index.tsx:827 msgid "Reset onboarding state" -msgstr "" +msgstr "Onboarding-Status zurücksetzen" #: src/view/com/auth/login/ForgotPasswordForm.tsx:104 msgid "Reset password" @@ -3321,28 +3135,28 @@ msgstr "Passwort zurücksetzen" #: src/view/screens/Settings/index.tsx:814 msgid "Reset preferences" -msgstr "" +msgstr "Einstellungen zurücksetzen" #: src/view/screens/Settings/index.tsx:817 msgid "Reset preferences state" -msgstr "" +msgstr "Einstellungen zurücksetzen" #: src/view/screens/Settings/index.tsx:825 msgid "Resets the onboarding state" -msgstr "" +msgstr "Setzt den Onboarding-Status zurück" #: src/view/screens/Settings/index.tsx:815 msgid "Resets the preferences state" -msgstr "" +msgstr "Einstellungen zurücksetzen" #: src/view/com/auth/login/LoginForm.tsx:269 msgid "Retries login" -msgstr "" +msgstr "Versucht die Anmeldung erneut" #: 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 "" +msgstr "Wiederholung der letzten Aktion, bei der ein Fehler aufgetreten ist" #: src/screens/Onboarding/StepInterests/index.tsx:221 #: src/screens/Onboarding/StepInterests/index.tsx:224 @@ -3355,23 +3169,15 @@ msgstr "" msgid "Retry" msgstr "Wiederholen" -#: src/view/com/auth/create/Step2.tsx:247 -#~ msgid "Retry." -#~ msgstr "" - #: src/view/screens/ProfileList.tsx:903 msgid "Return to previous page" -msgstr "" - -#: src/view/shell/desktop/RightNav.tsx:55 -#~ msgid "SANDBOX. Posts and accounts are not permanent." -#~ msgstr "" +msgstr "Zurück zur vorherigen Seite" #: src/view/com/lightbox/Lightbox.tsx:132 #: src/view/com/modals/CreateOrEditList.tsx:345 msgctxt "action" msgid "Save" -msgstr "" +msgstr "Speichern" #: src/view/com/modals/BirthDateSettings.tsx:94 #: src/view/com/modals/BirthDateSettings.tsx:97 @@ -3404,19 +3210,19 @@ msgstr "Gespeicherte Feeds" #: src/view/com/modals/EditProfile.tsx:225 msgid "Saves any changes to your profile" -msgstr "" +msgstr "Speichert alle Änderungen an Deinem Profil" #: src/view/com/modals/ChangeHandle.tsx:171 msgid "Saves handle change to {handle}" -msgstr "" +msgstr "Speichert Handle-Änderung in {handle}" #: src/screens/Onboarding/index.tsx:36 msgid "Science" -msgstr "" +msgstr "Wissenschaft" #: src/view/screens/ProfileList.tsx:859 msgid "Scroll to top" -msgstr "" +msgstr "Zum Anfang blättern" #: src/Navigation.tsx:447 #: src/view/com/auth/LoggedOut.tsx:122 @@ -3438,23 +3244,15 @@ msgstr "Suche" #: src/view/screens/Search/Search.tsx:735 #: src/view/shell/desktop/Search.tsx:255 msgid "Search for \"{query}\"" -msgstr "" +msgstr "Suche nach \"{query}\"" #: src/components/TagMenu/index.tsx:145 msgid "Search for all posts by @{authorHandle} with tag {displayTag}" -msgstr "" - -#: src/components/TagMenu/index.tsx:145 -#~ msgid "Search for all posts by @{authorHandle} with tag {tag}" -#~ msgstr "" +msgstr "Nach allen Beiträgen von @{authorHandle} mit dem Tag {displayTag} suchen" #: src/components/TagMenu/index.tsx:94 msgid "Search for all posts with tag {displayTag}" -msgstr "" - -#: src/components/TagMenu/index.tsx:90 -#~ msgid "Search for all posts with tag {tag}" -#~ msgstr "" +msgstr "Nach allen Beiträgen mit dem Tag {displayTag} suchen" #: src/view/com/auth/LoggedOut.tsx:104 #: src/view/com/auth/LoggedOut.tsx:105 @@ -3468,31 +3266,23 @@ msgstr "Sicherheitsschritt erforderlich" #: src/components/TagMenu/index.web.tsx:66 msgid "See {truncatedTag} posts" -msgstr "" +msgstr "Siehe {truncatedTag}-Beiträge" #: src/components/TagMenu/index.web.tsx:83 msgid "See {truncatedTag} posts by user" -msgstr "" +msgstr "Siehe {truncatedTag}-Beiträge des Benutzers" #: src/components/TagMenu/index.tsx:128 msgid "See <0>{displayTag}</0> posts" -msgstr "" +msgstr "Siehe <0>{displayTag}</0>-Beiträge" #: src/components/TagMenu/index.tsx:187 msgid "See <0>{displayTag}</0> posts by this user" -msgstr "" - -#: src/components/TagMenu/index.tsx:128 -#~ msgid "See <0>{tag}</0> posts" -#~ msgstr "" - -#: src/components/TagMenu/index.tsx:189 -#~ msgid "See <0>{tag}</0> posts by this user" -#~ msgstr "" +msgstr "Siehe <0>{displayTag}</0>-Beiträge von diesem Benutzer" #: src/view/screens/SavedFeeds.tsx:163 msgid "See this guide" -msgstr "" +msgstr "Siehe diesen Leitfaden" #: src/view/com/auth/HomeLoggedOutCTA.tsx:39 msgid "See what's next" @@ -3500,11 +3290,7 @@ msgstr "Schau, was als nächstes kommt" #: src/view/com/util/Selector.tsx:106 msgid "Select {item}" -msgstr "" - -#: src/view/com/modals/ServerInput.tsx:75 -#~ msgid "Select Bluesky Social" -#~ msgstr "Wähle Bluesky Social" +msgstr "Wähle {item}" #: src/view/com/auth/login/Login.tsx:117 msgid "Select from an existing account" @@ -3512,7 +3298,7 @@ msgstr "Von einem bestehenden Konto auswählen" #: src/view/com/util/Selector.tsx:107 msgid "Select option {i} of {numItems}" -msgstr "" +msgstr "Wähle Option {i} von {numItems}" #: src/view/com/auth/create/Step1.tsx:96 #: src/view/com/auth/login/LoginForm.tsx:150 @@ -3521,23 +3307,19 @@ msgstr "Service auswählen" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:52 msgid "Select some accounts below to follow" -msgstr "" +msgstr "Wähle unten einige Konten aus, denen du folgen möchtest" #: src/view/com/auth/server-input/index.tsx:82 msgid "Select the service that hosts your data." -msgstr "" - -#: src/screens/Onboarding/StepModeration/index.tsx:49 -#~ msgid "Select the types of content that you want to see (or not see), and we'll handle the rest." -#~ msgstr "" +msgstr "Wähle den Dienst aus, der deine Daten hostet." #: src/screens/Onboarding/StepTopicalFeeds.tsx:96 msgid "Select topical feeds to follow from the list below" -msgstr "" +msgstr "Wähle aus der folgenden Liste die themenbezogenen Feeds aus, die du verfolgen möchtest" #: src/screens/Onboarding/StepModeration/index.tsx:75 msgid "Select what you want to see (or not see), and we’ll handle the rest." -msgstr "" +msgstr "Wähle aus, was du sehen (oder nicht sehen) möchtest, und wir kümmern uns um den Rest." #: src/view/screens/LanguageSettings.tsx:281 msgid "Select which languages you want your subscribed feeds to include. If none are selected, all languages will be shown." @@ -3549,11 +3331,7 @@ msgstr "Wählen deine App-Sprache für den Standardtext aus, der in der App ange #: src/screens/Onboarding/StepInterests/index.tsx:196 msgid "Select your interests from the options below" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:155 -#~ msgid "Select your phone's country" -#~ msgstr "" +msgstr "Wähle aus den folgenden Optionen deine Interessen aus" #: src/view/screens/LanguageSettings.tsx:190 msgid "Select your preferred language for translations in your feed." @@ -3561,11 +3339,11 @@ msgstr "Wähle deine bevorzugte Sprache für die Übersetzungen in deinem Feed a #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:116 msgid "Select your primary algorithmic feeds" -msgstr "" +msgstr "Wähle deine primären algorithmischen Feeds" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:142 msgid "Select your secondary algorithmic feeds" -msgstr "" +msgstr "Wähle deine sekundären algorithmischen Feeds" #: src/view/com/modals/VerifyEmail.tsx:202 #: src/view/com/modals/VerifyEmail.tsx:204 @@ -3588,45 +3366,45 @@ msgstr "Feedback senden" #: src/view/com/modals/report/SendReportButton.tsx:45 msgid "Send Report" -msgstr "" +msgstr "Bericht senden" #: src/view/com/modals/DeleteAccount.tsx:133 msgid "Sends email with confirmation code for account deletion" -msgstr "" +msgstr "Sendet eine E-Mail mit Bestätigungscode für die Kontolöschung" #: src/view/com/auth/server-input/index.tsx:110 msgid "Server address" -msgstr "" +msgstr "Server-Adresse" #: src/view/com/modals/ContentFilteringSettings.tsx:311 msgid "Set {value} for {labelGroup} content moderation policy" -msgstr "" +msgstr "Legt {value} für die {labelGroup} Inhaltsmoderationsrichtlinie fest" #: src/view/com/modals/ContentFilteringSettings.tsx:160 #: src/view/com/modals/ContentFilteringSettings.tsx:179 msgctxt "action" msgid "Set Age" -msgstr "" +msgstr "Alter festlegen" #: src/view/screens/Settings/index.tsx:488 msgid "Set color theme to dark" -msgstr "" +msgstr "Farbthema auf dunkel einstellen" #: src/view/screens/Settings/index.tsx:481 msgid "Set color theme to light" -msgstr "" +msgstr "Farbthema auf hell einstellen" #: src/view/screens/Settings/index.tsx:475 msgid "Set color theme to system setting" -msgstr "" +msgstr "Farbthema auf Systemeinstellung setzen" #: src/view/screens/Settings/index.tsx:514 msgid "Set dark theme to the dark theme" -msgstr "" +msgstr "Dunkles Thema auf das dunkle Thema einstellen" #: src/view/screens/Settings/index.tsx:507 msgid "Set dark theme to the dim theme" -msgstr "" +msgstr "Dunkles Thema auf das gedämpfte Thema einstellen" #: src/view/com/auth/login/SetNewPasswordForm.tsx:104 msgid "Set new password" @@ -3634,7 +3412,7 @@ msgstr "Neues Passwort festlegen" #: src/view/com/auth/create/Step1.tsx:202 msgid "Set password" -msgstr "" +msgstr "Passwort festlegen" #: src/view/screens/PreferencesFollowingFeed.tsx:225 msgid "Set this setting to \"No\" to hide all quote posts from your feed. Reposts will still be visible." @@ -3652,34 +3430,30 @@ msgstr "Setze diese Einstellung auf \"Nein\", um alle Reposts aus deinem Feed au msgid "Set this setting to \"Yes\" to show replies in a threaded view. This is an experimental feature." msgstr "Setze diese Einstellung auf \"Ja\", um Antworten in einer Thread-Ansicht anzuzeigen. Dies ist eine experimentelle Funktion." -#: src/view/screens/PreferencesHomeFeed.tsx:261 -#~ msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your following feed. This is an experimental feature." -#~ msgstr "Setze diese Einstellung auf \"Ja\", um Beispiele für deine gespeicherten Feeds in deinem folgenden Feed anzuzeigen. Dies ist eine experimentelle Funktion." - #: src/view/screens/PreferencesFollowingFeed.tsx:261 msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your Following feed. This is an experimental feature." -msgstr "" +msgstr "Setze diese Einstellung auf \"Ja\", um Beispiele für deine gespeicherten Feeds in deinem Following-Feed anzuzeigen. Dies ist eine experimentelle Funktion." #: src/screens/Onboarding/Layout.tsx:50 msgid "Set up your account" -msgstr "" +msgstr "Dein Konto einrichten" #: src/view/com/modals/ChangeHandle.tsx:266 msgid "Sets Bluesky username" -msgstr "" +msgstr "Legt deinen Bluesky-Benutzernamen fest" #: src/view/com/auth/login/ForgotPasswordForm.tsx:157 msgid "Sets email for password reset" -msgstr "" +msgstr "Legt die E-Mail für das Zurücksetzen des Passworts fest" #: src/view/com/auth/login/ForgotPasswordForm.tsx:122 msgid "Sets hosting provider for password reset" -msgstr "" +msgstr "Legt den Hosting-Anbieter für das Zurücksetzen des Passworts fest" #: src/view/com/auth/create/Step1.tsx:97 #: src/view/com/auth/login/LoginForm.tsx:151 msgid "Sets server for the Bluesky client" -msgstr "" +msgstr "Setzt den Server für den Bluesky-Client" #: src/Navigation.tsx:137 #: src/view/screens/Settings/index.tsx:294 @@ -3696,7 +3470,7 @@ msgstr "Sexuelle Aktivitäten oder erotische Nacktheit." #: src/view/com/lightbox/Lightbox.tsx:141 msgctxt "action" msgid "Share" -msgstr "" +msgstr "Teilen" #: src/view/com/profile/ProfileHeader.tsx:295 #: src/view/com/util/forms/PostDropdownBtn.tsx:231 @@ -3720,7 +3494,7 @@ msgstr "Anzeigen" #: src/view/screens/PreferencesFollowingFeed.tsx:68 msgid "Show all replies" -msgstr "" +msgstr "Alle Antworten anzeigen" #: src/view/com/util/moderation/ScreenHider.tsx:132 msgid "Show anyway" @@ -3728,17 +3502,17 @@ msgstr "Trotzdem anzeigen" #: src/view/com/modals/EmbedConsent.tsx:87 msgid "Show embeds from {0}" -msgstr "" +msgstr "Eingebettete Medien von {0} anzeigen" #: src/view/com/profile/ProfileHeader.tsx:459 msgid "Show follows similar to {0}" -msgstr "" +msgstr "Zeige ähnliche Konten wie {0}" #: src/view/com/post-thread/PostThreadItem.tsx:538 #: src/view/com/post/Post.tsx:198 #: src/view/com/posts/FeedItem.tsx:363 msgid "Show More" -msgstr "" +msgstr "Mehr anzeigen" #: src/view/screens/PreferencesFollowingFeed.tsx:258 msgid "Show Posts from My Feeds" @@ -3746,19 +3520,19 @@ msgstr "Beiträge aus meinen Feeds anzeigen" #: src/view/screens/PreferencesFollowingFeed.tsx:222 msgid "Show Quote Posts" -msgstr "" +msgstr "Zitierte Beiträge anzeigen" #: src/screens/Onboarding/StepFollowingFeed.tsx:118 msgid "Show quote-posts in Following feed" -msgstr "" +msgstr "Zitierte Beiträge im Following Feed anzeigen" #: src/screens/Onboarding/StepFollowingFeed.tsx:134 msgid "Show quotes in Following" -msgstr "" +msgstr "Zitierte Beiträge im Following Feed anzeigen" #: src/screens/Onboarding/StepFollowingFeed.tsx:94 msgid "Show re-posts in Following feed" -msgstr "" +msgstr "Reposts im Following-Feed anzeigen" #: src/view/screens/PreferencesFollowingFeed.tsx:119 msgid "Show Replies" @@ -3770,15 +3544,15 @@ msgstr "Zeige Antworten von Personen, denen du folgst, vor allen anderen Antwort #: src/screens/Onboarding/StepFollowingFeed.tsx:86 msgid "Show replies in Following" -msgstr "" +msgstr "Antworten in folgendem Feed anzeigen" #: src/screens/Onboarding/StepFollowingFeed.tsx:70 msgid "Show replies in Following feed" -msgstr "" +msgstr "Antworten in folgendem Feed anzeigen" #: src/view/screens/PreferencesFollowingFeed.tsx:70 msgid "Show replies with at least {value} {0}" -msgstr "" +msgstr "Antworten mit mindestens {value} {0} anzeigen" #: src/view/screens/PreferencesFollowingFeed.tsx:188 msgid "Show Reposts" @@ -3786,12 +3560,12 @@ msgstr "Reposts anzeigen" #: src/screens/Onboarding/StepFollowingFeed.tsx:110 msgid "Show reposts in Following" -msgstr "" +msgstr "Reposts im Following-Feed anzeigen" #: src/view/com/util/moderation/ContentHider.tsx:67 #: src/view/com/util/moderation/PostHider.tsx:61 msgid "Show the content" -msgstr "" +msgstr "Den Inhalt anzeigen" #: src/view/com/notifications/FeedItem.tsx:347 msgid "Show users" @@ -3799,12 +3573,12 @@ msgstr "Nutzer anzeigen" #: src/view/com/profile/ProfileHeader.tsx:462 msgid "Shows a list of users similar to this user." -msgstr "" +msgstr "Zeigt eine Liste von Benutzern, die diesem Benutzer ähnlich sind." #: src/view/com/post-thread/PostThreadFollowBtn.tsx:124 #: src/view/com/profile/ProfileHeader.tsx:506 msgid "Shows posts from {0} in your feed" -msgstr "" +msgstr "Zeigt Beiträge von {0} in deinem Feed" #: src/view/com/auth/HomeLoggedOutCTA.tsx:70 #: src/view/com/auth/login/Login.tsx:98 @@ -3872,11 +3646,11 @@ msgstr "Angemeldet als" #: src/view/com/auth/login/ChooseAccountForm.tsx:103 msgid "Signed in as @{0}" -msgstr "" +msgstr "Angemeldet als @{0}" #: src/view/com/modals/SwitchAccount.tsx:66 msgid "Signs {0} out of Bluesky" -msgstr "" +msgstr "Meldet {0} von Bluesky ab" #: src/screens/Onboarding/StepInterests/index.tsx:235 #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:195 @@ -3886,31 +3660,19 @@ msgstr "Überspringen" #: src/screens/Onboarding/StepInterests/index.tsx:232 msgid "Skip this flow" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:82 -#~ msgid "SMS verification" -#~ msgstr "" +msgstr "Diesen Schritt überspringen" #: src/screens/Onboarding/index.tsx:40 msgid "Software Dev" -msgstr "" - -#: src/view/com/modals/ProfilePreview.tsx:62 -#~ msgid "Something went wrong and we're not sure what." -#~ msgstr "" +msgstr "Software-Entwicklung" #: src/components/Lists.tsx:203 msgid "Something went wrong!" -msgstr "" - -#: src/view/com/modals/Waitlist.tsx:51 -#~ msgid "Something went wrong. Check your email and try again." -#~ msgstr "" +msgstr "Es ist ein Fehler aufgetreten." #: src/App.native.tsx:66 msgid "Sorry! Your session expired. Please log in again." -msgstr "" +msgstr "Entschuldigung! Deine Sitzung ist abgelaufen. Bitte logge dich erneut ein." #: src/view/screens/PreferencesThreads.tsx:69 msgid "Sort Replies" @@ -3922,15 +3684,11 @@ msgstr "Antworten auf denselben Beitrag sortieren nach:" #: src/screens/Onboarding/index.tsx:30 msgid "Sports" -msgstr "" +msgstr "Sport" #: src/view/com/modals/crop-image/CropImage.web.tsx:122 msgid "Square" -msgstr "" - -#: src/view/com/modals/ServerInput.tsx:62 -#~ msgid "Staging" -#~ msgstr "Staging" +msgstr "Quadratische" #: src/view/screens/Settings/index.tsx:871 msgid "Status page" @@ -3938,16 +3696,16 @@ msgstr "Status-Seite" #: src/view/com/auth/create/StepHeader.tsx:22 msgid "Step {0} of {numSteps}" -msgstr "" +msgstr "Schritt {0} von {numSteps}" #: src/view/screens/Settings/index.tsx:274 msgid "Storage cleared, you need to restart the app now." -msgstr "" +msgstr "Der Speicher wurde gelöscht, du musst die App jetzt neu starten." #: src/Navigation.tsx:204 #: src/view/screens/Settings/index.tsx:807 msgid "Storybook" -msgstr "" +msgstr "Geschichtenbuch" #: src/view/com/modals/AppealLabel.tsx:101 msgid "Submit" @@ -3960,7 +3718,7 @@ msgstr "Abonnieren" #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:173 #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:308 msgid "Subscribe to the {0} feed" -msgstr "" +msgstr "Abonniere den {0} Feed" #: src/view/screens/ProfileList.tsx:604 msgid "Subscribe to this list" @@ -3972,11 +3730,11 @@ msgstr "Vorgeschlagene Follower" #: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:64 msgid "Suggested for you" -msgstr "" +msgstr "Vorgeschlagen für dich" #: src/view/com/modals/SelfLabel.tsx:95 msgid "Suggestive" -msgstr "" +msgstr "Suggestiv" #: src/Navigation.tsx:214 #: src/view/screens/Support.tsx:30 @@ -3984,10 +3742,6 @@ msgstr "" msgid "Support" msgstr "Support" -#: src/view/com/modals/ProfilePreview.tsx:110 -#~ msgid "Swipe up to see more" -#~ msgstr "" - #: src/view/com/modals/SwitchAccount.tsx:117 msgid "Switch Account" msgstr "Konto wechseln" @@ -3995,16 +3749,16 @@ msgstr "Konto wechseln" #: src/view/com/modals/SwitchAccount.tsx:97 #: src/view/screens/Settings/index.tsx:130 msgid "Switch to {0}" -msgstr "" +msgstr "Wechseln zu {0}" #: src/view/com/modals/SwitchAccount.tsx:98 #: src/view/screens/Settings/index.tsx:131 msgid "Switches the account you are logged in to" -msgstr "" +msgstr "Wechselt das Konto, in das du eingeloggt bist" #: src/view/screens/Settings/index.tsx:472 msgid "System" -msgstr "" +msgstr "System" #: src/view/screens/Settings/index.tsx:795 msgid "System log" @@ -4012,15 +3766,11 @@ msgstr "Systemprotokoll" #: src/components/dialogs/MutedWords.tsx:337 msgid "tag" -msgstr "" +msgstr "Tag" #: src/components/TagMenu/index.tsx:78 msgid "Tag menu: {displayTag}" -msgstr "" - -#: src/components/TagMenu/index.tsx:74 -#~ msgid "Tag menu: {tag}" -#~ msgstr "" +msgstr "Tag-Menü: {displayTag}" #: src/view/com/modals/crop-image/CropImage.web.tsx:112 msgid "Tall" @@ -4028,11 +3778,11 @@ msgstr "Groß" #: src/view/com/util/images/AutoSizedImage.tsx:70 msgid "Tap to view fully" -msgstr "" +msgstr "Tippe, um die vollständige Ansicht anzuzeigen" #: src/screens/Onboarding/index.tsx:39 msgid "Tech" -msgstr "" +msgstr "Technik" #: src/view/shell/desktop/RightNav.tsx:81 msgid "Terms" @@ -4047,7 +3797,7 @@ msgstr "Nutzungsbedingungen" #: src/components/dialogs/MutedWords.tsx:337 msgid "text" -msgstr "" +msgstr "Text" #: src/view/com/modals/AppealLabel.tsx:70 #: src/view/com/modals/report/InputIssueDetails.tsx:51 @@ -4056,11 +3806,11 @@ msgstr "Text-Eingabefeld" #: src/view/com/auth/create/CreateAccount.tsx:94 msgid "That handle is already taken." -msgstr "" +msgstr "Dieser Handle ist bereits besetzt." #: src/view/com/profile/ProfileHeader.tsx:263 msgid "The account will be able to interact with you after unblocking." -msgstr "" +msgstr "Das Konto kann nach der Entblockiert mit dir interagieren." #: src/view/screens/CommunityGuidelines.tsx:36 msgid "The Community Guidelines have been moved to <0/>" @@ -4068,11 +3818,11 @@ msgstr "Die Community-Richtlinien wurden nach <0/> verschoben" #: src/view/screens/CopyrightPolicy.tsx:33 msgid "The Copyright Policy has been moved to <0/>" -msgstr "" +msgstr "Die Copyright-Richtlinie wurde nach <0/> verschoben" #: src/screens/Onboarding/Layout.tsx:60 msgid "The following steps will help customize your Bluesky experience." -msgstr "" +msgstr "Die folgenden Schritte helfen dir, dein Bluesky-Erlebnis anzupassen." #: src/view/com/post-thread/PostThread.tsx:517 msgid "The post may have been deleted." @@ -4088,23 +3838,23 @@ msgstr "Das Support-Formular wurde verschoben. Wenn du Hilfe benötigst, wende d #: src/view/screens/TermsOfService.tsx:33 msgid "The Terms of Service have been moved to" -msgstr "" +msgstr "Die Allgemeinen Geschäftsbedingungen wurden verschoben nach" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:150 msgid "There are many feeds to try:" -msgstr "" +msgstr "Es gibt viele Feeds zum Ausprobieren:" #: src/view/screens/ProfileFeed.tsx:550 msgid "There was an an issue contacting the server, please check your internet connection and try again." -msgstr "" +msgstr "Es gab ein Problem bei der Kontaktaufnahme mit dem Server. Bitte überprüfe deine Internetverbindung und versuche es erneut." #: src/view/com/posts/FeedErrorMessage.tsx:139 msgid "There was an an issue removing this feed. Please check your internet connection and try again." -msgstr "" +msgstr "Es gab ein Problem beim Entfernen dieses Feeds. Bitte überprüfe deine Internetverbindung und versuche es erneut." #: src/view/screens/ProfileFeed.tsx:210 msgid "There was an an issue updating your feeds, please check your internet connection and try again." -msgstr "" +msgstr "Es gab ein Problem bei der Aktualisierung deines Feeds. Bitte überprüfe deine Internetverbindung und versuche es erneut." #: src/view/screens/ProfileFeed.tsx:237 #: src/view/screens/ProfileList.tsx:267 @@ -4112,7 +3862,7 @@ msgstr "" #: src/view/screens/SavedFeeds.tsx:231 #: src/view/screens/SavedFeeds.tsx:252 msgid "There was an issue contacting the server" -msgstr "" +msgstr "Es gab ein Problem bei der Kontaktaufnahme mit dem Server" #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:57 #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:66 @@ -4120,33 +3870,33 @@ msgstr "" #: src/view/com/feeds/FeedSourceCard.tsx:129 #: src/view/com/feeds/FeedSourceCard.tsx:183 msgid "There was an issue contacting your server" -msgstr "" +msgstr "Es gab ein Problem bei der Kontaktaufnahme mit deinem Server" #: src/view/com/notifications/Feed.tsx:117 msgid "There was an issue fetching notifications. Tap here to try again." -msgstr "" +msgstr "Es gab ein Problem beim Abrufen von Mitteilungen. Tippe hier, um es erneut zu versuchen." #: src/view/com/posts/Feed.tsx:265 msgid "There was an issue fetching posts. Tap here to try again." -msgstr "" +msgstr "Es gab ein Problem beim Abrufen der Beiträge. Tippe hier, um es erneut zu versuchen." #: src/view/com/lists/ListMembers.tsx:172 msgid "There was an issue fetching the list. Tap here to try again." -msgstr "" +msgstr "Es gab ein Problem beim Abrufen der Liste. Tippe hier, um es erneut zu versuchen." #: src/view/com/feeds/ProfileFeedgens.tsx:148 #: src/view/com/lists/ProfileLists.tsx:155 msgid "There was an issue fetching your lists. Tap here to try again." -msgstr "" +msgstr "Es gab ein Problem beim Abrufen deiner Listen. Tippe hier, um es erneut zu versuchen." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:63 #: src/view/com/modals/ContentFilteringSettings.tsx:126 msgid "There was an issue syncing your preferences with the server" -msgstr "" +msgstr "Es gab ein Problem bei der Synchronisierung deiner Einstellungen mit dem Server" #: src/view/screens/AppPasswords.tsx:66 msgid "There was an issue with fetching your app passwords" -msgstr "" +msgstr "Es gab ein Problem beim Abrufen deiner App-Passwörter" #: src/view/com/post-thread/PostThreadFollowBtn.tsx:93 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:105 @@ -4157,46 +3907,42 @@ msgstr "" #: src/view/com/profile/ProfileHeader.tsx:250 #: src/view/com/profile/ProfileHeader.tsx:272 msgid "There was an issue! {0}" -msgstr "" +msgstr "Es gab ein Problem! {0}" #: src/view/screens/ProfileList.tsx:288 #: src/view/screens/ProfileList.tsx:307 #: src/view/screens/ProfileList.tsx:329 #: src/view/screens/ProfileList.tsx:348 msgid "There was an issue. Please check your internet connection and try again." -msgstr "" +msgstr "Es ist ein Problem aufgetreten. Bitte überprüfe deine Internetverbindung und versuche es erneut." #: src/view/com/util/ErrorBoundary.tsx:36 msgid "There was an unexpected issue in the application. Please let us know if this happened to you!" -msgstr "" +msgstr "Es gab ein unerwartetes Problem in der Anwendung. Bitte teile uns mit, wenn dies bei dir der Fall ist!" #: src/screens/Deactivated.tsx:106 msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:55 -#~ msgid "There's something wrong with this number. Please choose your country and enter your full phone number!" -#~ msgstr "" +msgstr "Es gab einen Ansturm neuer Nutzer auf Bluesky! Wir werden dein Konto so schnell wie möglich aktivieren." #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:138 msgid "These are popular accounts you might like:" -msgstr "" +msgstr "Dies sind beliebte Konten, die dir gefallen könnten:" #: src/view/com/util/moderation/ScreenHider.tsx:88 msgid "This {screenDescription} has been flagged:" -msgstr "" +msgstr "Diese {screenDescription} wurde gekennzeichnet:" #: src/view/com/util/moderation/ScreenHider.tsx:83 msgid "This account has requested that users sign in to view their profile." -msgstr "" +msgstr "Dieses Konto hat die Benutzer aufgefordert, sich anzumelden, um dein Profil zu sehen." #: src/view/com/modals/EmbedConsent.tsx:68 msgid "This content is hosted by {0}. Do you want to enable external media?" -msgstr "" +msgstr "Dieser Inhalt wird von {0} gehostet. Möchtest du externe Medien aktivieren?" #: src/view/com/modals/ModerationDetails.tsx:67 msgid "This content is not available because one of the users involved has blocked the other." -msgstr "" +msgstr "Dieser Inhalt ist nicht verfügbar, weil einer der beteiligten Nutzer den anderen blockiert hat." #: src/view/com/posts/FeedErrorMessage.tsx:108 msgid "This content is not viewable without a Bluesky account." @@ -4204,7 +3950,7 @@ msgstr "Dieser Inhalt ist ohne ein Bluesky-Konto nicht sichtbar." #: src/view/screens/Settings/ExportCarDialog.tsx:75 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost.</0>" -msgstr "" +msgstr "Diese Funktion befindet sich in der Beta-Phase. Du kannst mehr über Kontodepot-Exporte in <0>diesem Blogpost lesen.</0>" #: src/view/com/posts/FeedErrorMessage.tsx:114 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." @@ -4214,11 +3960,11 @@ msgstr "Dieser Feed wird derzeit stark frequentiert und ist vorübergehend nicht #: src/view/screens/ProfileFeed.tsx:476 #: src/view/screens/ProfileList.tsx:661 msgid "This feed is empty!" -msgstr "" +msgstr "Dieser Feed ist leer!" #: 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 "" +msgstr "Dieser Feed ist leer! Möglicherweise musst du mehr Benutzern folgen oder deine Spracheinstellungen anpassen." #: src/view/com/modals/BirthDateSettings.tsx:61 msgid "This information is not shared with other users." @@ -4234,11 +3980,11 @@ msgstr "Dieser Link führt dich auf die folgende Website:" #: src/view/screens/ProfileList.tsx:839 msgid "This list is empty!" -msgstr "" +msgstr "Diese Liste ist leer!" #: src/view/com/modals/AddAppPasswords.tsx:106 msgid "This name is already in use" -msgstr "" +msgstr "Dieser Name ist bereits in Gebrauch" #: src/view/com/post-thread/PostThreadItem.tsx:125 msgid "This post has been deleted." @@ -4246,19 +3992,15 @@ msgstr "Dieser Beitrag wurde gelöscht." #: src/view/com/modals/ModerationDetails.tsx:62 msgid "This user has blocked you. You cannot view their content." -msgstr "" +msgstr "Dieser Benutzer hat dich blockiert. Du kannst deren Inhalte nicht sehen." #: src/view/com/modals/ModerationDetails.tsx:42 msgid "This user is included in the <0/> list which you have blocked." -msgstr "" +msgstr "Dieser Benutzer ist in der Liste <0/> enthalten, die du blockiert hast." #: src/view/com/modals/ModerationDetails.tsx:74 msgid "This user is included in the <0/> list which you have muted." -msgstr "" - -#: src/view/com/modals/ModerationDetails.tsx:74 -#~ msgid "This user is included the <0/> list which you have muted." -#~ msgstr "" +msgstr "Dieser Benutzer ist in der Liste <0/> enthalten, die du stummgeschaltet haben." #: src/view/com/modals/SelfLabel.tsx:137 msgid "This warning is only available for posts with media attached." @@ -4266,7 +4008,7 @@ msgstr "Diese Warnung ist nur für Beiträge mit angehängten Medien verfügbar. #: src/components/dialogs/MutedWords.tsx:285 msgid "This will delete {0} from your muted words. You can always add it back later." -msgstr "" +msgstr "Dies wird {0} aus deinen stummgeschalteten Wörtern löschen. Du kannst es später jederzeit wieder hinzufügen." #: src/view/com/util/forms/PostDropdownBtn.tsx:282 msgid "This will hide this post from your feeds." @@ -4279,23 +4021,23 @@ msgstr "Thread-Einstellungen" #: src/view/screens/PreferencesThreads.tsx:119 msgid "Threaded Mode" -msgstr "" +msgstr "Gewindemodus" #: src/Navigation.tsx:257 msgid "Threads Preferences" -msgstr "" +msgstr "Thread-Einstellungen" #: src/components/dialogs/MutedWords.tsx:113 msgid "Toggle between muted word options." -msgstr "" +msgstr "Zwischen den Optionen für stummgeschaltete Wörter wechseln." #: src/view/com/util/forms/DropdownButton.tsx:246 msgid "Toggle dropdown" -msgstr "" +msgstr "Dieses Dropdown umschalten" #: src/view/com/modals/EditImage.tsx:271 msgid "Transformations" -msgstr "" +msgstr "Verwandlungen" #: src/view/com/post-thread/PostThreadItem.tsx:685 #: src/view/com/post-thread/PostThreadItem.tsx:687 @@ -4311,11 +4053,11 @@ msgstr "Erneut versuchen" #: src/view/screens/ProfileList.tsx:506 msgid "Un-block list" -msgstr "" +msgstr "Liste entblocken" #: src/view/screens/ProfileList.tsx:491 msgid "Un-mute list" -msgstr "" +msgstr "Stummschaltung von Liste aufheben" #: src/view/com/auth/create/CreateAccount.tsx:58 #: src/view/com/auth/login/ForgotPasswordForm.tsx:87 @@ -4323,22 +4065,22 @@ msgstr "" #: src/view/com/auth/login/LoginForm.tsx:118 #: src/view/com/modals/ChangePassword.tsx:70 msgid "Unable to contact your service. Please check your Internet connection." -msgstr "" +msgstr "Es ist uns nicht gelungen, deinen Dienst zu kontaktieren. Bitte überprüfe deine Internetverbindung." #: src/view/com/profile/ProfileHeader.tsx:433 #: src/view/screens/ProfileList.tsx:590 msgid "Unblock" -msgstr "" +msgstr "Entblocken" #: src/view/com/profile/ProfileHeader.tsx:436 msgctxt "action" msgid "Unblock" -msgstr "" +msgstr "Entblocken" #: src/view/com/profile/ProfileHeader.tsx:261 #: src/view/com/profile/ProfileHeader.tsx:345 msgid "Unblock Account" -msgstr "" +msgstr "Konto entblocken" #: src/view/com/modals/Repost.tsx:42 #: src/view/com/modals/Repost.tsx:55 @@ -4350,11 +4092,11 @@ msgstr "Repost rückgängig machen" #: src/view/com/profile/FollowButton.tsx:55 msgctxt "action" msgid "Unfollow" -msgstr "" +msgstr "Nicht mehr folgen" #: src/view/com/profile/ProfileHeader.tsx:485 msgid "Unfollow {0}" -msgstr "" +msgstr "{0} nicht mehr folgen" #: src/view/com/auth/create/state.ts:262 msgid "Unfortunately, you do not meet the requirements to create an account." @@ -4362,50 +4104,46 @@ msgstr "Leider erfüllst du nicht die Voraussetzungen, um einen Account zu erste #: src/view/com/util/post-ctrls/PostCtrls.tsx:182 msgid "Unlike" -msgstr "" +msgstr "Like aufheben" #: src/components/TagMenu/index.tsx:249 #: src/view/screens/ProfileList.tsx:597 msgid "Unmute" -msgstr "" +msgstr "Stummschaltung aufheben" #: src/components/TagMenu/index.web.tsx:104 msgid "Unmute {truncatedTag}" -msgstr "" +msgstr "Stummschaltung von {truncatedTag} aufheben" #: src/view/com/profile/ProfileHeader.tsx:326 msgid "Unmute Account" -msgstr "" +msgstr "Stummschaltung von Konto aufheben" #: src/components/TagMenu/index.tsx:208 msgid "Unmute all {displayTag} posts" -msgstr "" - -#: src/components/TagMenu/index.tsx:210 -#~ msgid "Unmute all {tag} posts" -#~ msgstr "" +msgstr "Stummschaltung aller {displayTag}-Beiträge aufheben" #: src/view/com/util/forms/PostDropdownBtn.tsx:251 #: src/view/com/util/forms/PostDropdownBtn.tsx:256 msgid "Unmute thread" -msgstr "" +msgstr "Stummschaltung von Thread aufheben" #: src/view/screens/ProfileFeed.tsx:354 #: src/view/screens/ProfileList.tsx:581 msgid "Unpin" -msgstr "" +msgstr "Anheften aufheben" #: src/view/screens/ProfileList.tsx:474 msgid "Unpin moderation list" -msgstr "" +msgstr "Anheften der Moderationsliste aufheben" #: src/view/screens/ProfileFeed.tsx:346 msgid "Unsave" -msgstr "" +msgstr "Speicherung aufheben" #: src/view/com/modals/UserAddRemoveLists.tsx:70 msgid "Update {displayName} in Lists" -msgstr "" +msgstr "{displayName} in Listen aktualisieren" #: src/lib/hooks/useOTAUpdate.ts:15 msgid "Update Available" @@ -4417,7 +4155,7 @@ msgstr "Aktualisieren..." #: src/view/com/modals/ChangeHandle.tsx:455 msgid "Upload a text file to:" -msgstr "" +msgstr "Hochladen einer Textdatei auf:" #: src/view/screens/AppPasswords.tsx:195 msgid "Use app passwords to login to other Bluesky clients without giving full access to your account or password." @@ -4430,36 +4168,32 @@ msgstr "Standardanbieter verwenden" #: src/view/com/modals/InAppBrowserConsent.tsx:56 #: src/view/com/modals/InAppBrowserConsent.tsx:58 msgid "Use in-app browser" -msgstr "" +msgstr "In-App-Browser verwenden" #: src/view/com/modals/InAppBrowserConsent.tsx:66 #: src/view/com/modals/InAppBrowserConsent.tsx:68 msgid "Use my default browser" -msgstr "" +msgstr "Meinen Standardbrowser verwenden" #: src/view/com/modals/AddAppPasswords.tsx:155 msgid "Use this to sign into the other app along with your handle." msgstr "Verwenden dies, um dich mit deinem Handle bei der anderen App einzuloggen." -#: src/view/com/modals/ServerInput.tsx:105 -#~ msgid "Use your domain as your Bluesky client service provider" -#~ msgstr "" - #: src/view/com/modals/InviteCodes.tsx:200 msgid "Used by:" msgstr "Verwendet von:" #: src/view/com/modals/ModerationDetails.tsx:54 msgid "User Blocked" -msgstr "" +msgstr "Benutzer blockiert" #: src/view/com/modals/ModerationDetails.tsx:40 msgid "User Blocked by List" -msgstr "" +msgstr "Benutzer durch der Liste blockiert" #: src/view/com/modals/ModerationDetails.tsx:60 msgid "User Blocks You" -msgstr "" +msgstr "Benutzer blockiert dich" #: src/view/com/auth/create/Step2.tsx:79 msgid "User handle" @@ -4468,25 +4202,25 @@ msgstr "Benutzerhandle" #: src/view/com/lists/ListCard.tsx:85 #: src/view/com/modals/UserAddRemoveLists.tsx:198 msgid "User list by {0}" -msgstr "" +msgstr "Benutzerliste von {0}" #: src/view/screens/ProfileList.tsx:763 msgid "User list by <0/>" -msgstr "" +msgstr "Benutzerliste von <0/>" #: src/view/com/lists/ListCard.tsx:83 #: src/view/com/modals/UserAddRemoveLists.tsx:196 #: src/view/screens/ProfileList.tsx:761 msgid "User list by you" -msgstr "" +msgstr "Benutzerliste von dir" #: src/view/com/modals/CreateOrEditList.tsx:196 msgid "User list created" -msgstr "" +msgstr "Benutzerliste erstellt" #: src/view/com/modals/CreateOrEditList.tsx:182 msgid "User list updated" -msgstr "" +msgstr "Benutzerliste aktualisiert" #: src/view/screens/Lists.tsx:58 msgid "User Lists" @@ -4507,11 +4241,7 @@ msgstr "Nutzer gefolgt von <0/>" #: src/view/com/modals/Threadgate.tsx:106 msgid "Users in \"{0}\"" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:243 -#~ msgid "Verification code" -#~ msgstr "" +msgstr "Benutzer in \"{0}\"" #: src/view/screens/Settings/index.tsx:910 msgid "Verify email" @@ -4532,15 +4262,15 @@ msgstr "Neue E-Mail bestätigen" #: src/view/com/modals/VerifyEmail.tsx:103 msgid "Verify Your Email" -msgstr "" +msgstr "Überprüfe deine E-Mail" #: src/screens/Onboarding/index.tsx:42 msgid "Video Games" -msgstr "" +msgstr "Videospiele" #: src/view/com/profile/ProfileHeader.tsx:662 msgid "View {0}'s avatar" -msgstr "" +msgstr "Avatar {0} ansehen" #: src/view/screens/Log.tsx:52 msgid "View debug entry" @@ -4548,11 +4278,11 @@ msgstr "Debug-Eintrag anzeigen" #: src/view/com/posts/FeedSlice.tsx:103 msgid "View full thread" -msgstr "" +msgstr "Vollständigen Thread ansehen" #: src/view/com/posts/FeedErrorMessage.tsx:172 msgid "View profile" -msgstr "" +msgstr "Profil ansehen" #: src/view/com/profile/ProfileSubpageHeader.tsx:128 msgid "View the avatar" @@ -4565,55 +4295,51 @@ msgstr "Seite ansehen" #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:42 #: src/view/com/modals/ContentFilteringSettings.tsx:259 msgid "Warn" -msgstr "" +msgstr "Warnen" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:134 msgid "We also think you'll like \"For You\" by Skygaze:" -msgstr "" +msgstr "Wir glauben auch, dass dir \"For You\" von Skygaze gefallen wird:" #: src/screens/Hashtag.tsx:132 msgid "We couldn't find any results for that hashtag." -msgstr "" +msgstr "Wir konnten keine Ergebnisse für diesen Hashtag finden." #: src/screens/Deactivated.tsx:133 msgid "We estimate {estimatedTime} until your account is ready." -msgstr "" +msgstr "Wir schätzen {estimatedTime} bis dein Konto bereit ist." #: src/screens/Onboarding/StepFinished.tsx:93 msgid "We hope you have a wonderful time. Remember, Bluesky is:" -msgstr "" +msgstr "Wir hoffen, dass du eine schöne Zeit hast. Denke daran, Bluesky ist:" #: src/view/com/posts/DiscoverFallbackHeader.tsx:29 msgid "We ran out of posts from your follows. Here's the latest from <0/>." -msgstr "" - -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:118 -#~ msgid "We recommend \"For You\" by Skygaze:" -#~ msgstr "" +msgstr "Wir haben keine Beiträge mehr von den Konten, denen du folgst. Hier ist das Neueste von <0/>." #: src/components/dialogs/MutedWords.tsx:204 msgid "We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." -msgstr "" +msgstr "Wir empfehlen, gebräuchliche Wörter zu vermeiden, die in vielen Beiträgen vorkommen, da dies dazu führen kann, dass keine Beiträge angezeigt werden." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124 msgid "We recommend our \"Discover\" feed:" -msgstr "" +msgstr "Wir empfehlen unser \"Discover\" Feed:" #: src/screens/Onboarding/StepInterests/index.tsx:133 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 "" +msgstr "Die Verbindung konnte nicht hergestellt werden. Bitte versuche es erneut, um mit der Einrichtung deines Kontos fortzufahren. Wenn der Versuch weiterhin fehlschlägt, kannst du diesen Schritt überspringen." #: src/screens/Deactivated.tsx:137 msgid "We will let you know when your account is ready." -msgstr "" +msgstr "Wir werden dich benachrichtigen, wenn dein Konto bereit ist." #: src/view/com/modals/AppealLabel.tsx:48 msgid "We'll look into your appeal promptly." -msgstr "" +msgstr "Wir werden deinen Widerspruch unverzüglich prüfen." #: src/screens/Onboarding/StepInterests/index.tsx:138 msgid "We'll use this to help customize your experience." -msgstr "" +msgstr "Wir verwenden diese Informationen, um dein Erlebnis individuell zu gestalten." #: src/view/com/auth/create/CreateAccount.tsx:134 msgid "We're so excited to have you join us!" @@ -4621,11 +4347,11 @@ msgstr "Wir freuen uns sehr, dass du dabei bist!" #: src/view/screens/ProfileList.tsx:86 msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." -msgstr "" +msgstr "Es tut uns leid, aber wir waren nicht in der Lage, diese Liste aufzulösen. Wenn das Problem weiterhin besteht, kontaktiere bitte den Ersteller der Liste, @{handleOrDid}." #: src/components/dialogs/MutedWords.tsx:230 msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." -msgstr "" +msgstr "Es tut uns leid, aber wir konnten deine stummgeschalteten Wörter nicht laden. Bitte versuche es erneut." #: src/view/screens/Search/Search.tsx:254 msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." @@ -4642,11 +4368,11 @@ msgstr "Willkommen bei <0>Bluesky</0>" #: src/screens/Onboarding/StepInterests/index.tsx:130 msgid "What are your interests?" -msgstr "" +msgstr "Was sind deine Interessen?" #: src/view/com/modals/report/Modal.tsx:169 msgid "What is the issue with this {collectionName}?" -msgstr "" +msgstr "Was ist das Problem mit diesem {collectionName}?" #: src/view/com/auth/SplashScreen.tsx:59 #: src/view/com/composer/Composer.tsx:286 @@ -4681,11 +4407,7 @@ msgstr "Schreibe deine Antwort" #: src/screens/Onboarding/index.tsx:28 msgid "Writers" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:263 -#~ msgid "XXXXXX" -#~ msgstr "" +msgstr "Schriftsteller" #: src/view/com/composer/select-language/SuggestedLanguage.tsx:77 #: src/view/screens/PreferencesFollowingFeed.tsx:129 @@ -4697,26 +4419,18 @@ msgstr "" msgid "Yes" msgstr "Ja" -#: src/screens/Onboarding/StepModeration/index.tsx:46 -#~ msgid "You are in control" -#~ msgstr "" - #: src/screens/Deactivated.tsx:130 msgid "You are in line." -msgstr "" +msgstr "Du befindest dich in der Warteschlange." #: src/view/com/posts/FollowingEmptyState.tsx:67 #: src/view/com/posts/FollowingEndOfFeed.tsx:68 msgid "You can also discover new Custom Feeds to follow." -msgstr "" - -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:123 -#~ msgid "You can also try our \"Discover\" algorithm:" -#~ msgstr "" +msgstr "Du kannst auch neue benutzerdefinierte Feeds entdecken und ihnen folgen." #: src/screens/Onboarding/StepFollowingFeed.tsx:142 msgid "You can change these settings later." -msgstr "" +msgstr "Du kannst diese Einstellungen später ändern." #: src/view/com/auth/login/Login.tsx:158 #: src/view/com/auth/login/PasswordUpdatedForm.tsx:31 @@ -4745,18 +4459,18 @@ msgstr "Du hast den Verfasser blockiert oder du wurdest vom Verfasser blockiert. #: src/view/com/modals/ModerationDetails.tsx:56 msgid "You have blocked this user. You cannot view their content." -msgstr "" +msgstr "Du hast diesen Benutzer blockiert und kannst seine Inhalte nicht sehen." #: src/view/com/auth/login/SetNewPasswordForm.tsx:57 #: src/view/com/auth/login/SetNewPasswordForm.tsx:92 #: src/view/com/modals/ChangePassword.tsx:87 #: src/view/com/modals/ChangePassword.tsx:121 msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." -msgstr "" +msgstr "Du hast einen ungültigen Code eingegeben. Er sollte wie XXXXX-XXXXX aussehen." #: src/view/com/modals/ModerationDetails.tsx:87 msgid "You have muted this user." -msgstr "" +msgstr "Du hast diesen Benutzer stummgeschaltet." #: src/view/com/feeds/ProfileFeedgens.tsx:136 msgid "You have no feeds." @@ -4769,7 +4483,7 @@ msgstr "Du hast keine Listen." #: src/view/screens/ModerationBlockedAccounts.tsx:132 msgid "You have not blocked any accounts yet. To block an account, go to their profile and selected \"Block account\" from the menu on their account." -msgstr "" +msgstr "Du hast noch keine Konten blockiert. Um ein Konto zu blockieren, gehe auf dessen Profil und wähle \"Konto blockieren\" aus dem Menü des Kontos aus." #: src/view/screens/AppPasswords.tsx:87 msgid "You have not created any app passwords yet. You can create one by pressing the button below." @@ -4777,27 +4491,27 @@ msgstr "Du hast noch keine App-Passwörter erstellt. Du kannst eines erstellen, #: src/view/screens/ModerationMutedAccounts.tsx:131 msgid "You have not muted any accounts yet. To mute an account, go to their profile and selected \"Mute account\" from the menu on their account." -msgstr "" +msgstr "Du hast noch keine Konten stummgeschaltet. Um ein Konto stumm zu schalten, gehe auf dessen Profil und wähle \"Konto stummschalten\" aus dem Menü des Kontos aus." #: src/components/dialogs/MutedWords.tsx:250 msgid "You haven't muted any words or tags yet" -msgstr "" +msgstr "Du hast noch keine Wörter oder Tags stummgeschaltet" #: src/view/com/modals/ContentFilteringSettings.tsx:175 msgid "You must be 18 or older to enable adult content." -msgstr "" +msgstr "Du musst 18 Jahre oder älter sein, um Inhalte für Erwachsene zu aktivieren." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:103 msgid "You must be 18 years or older to enable adult content" -msgstr "" +msgstr "Du musst 18 Jahre oder älter sein, um Inhalte für Erwachsene zu aktivieren." #: src/view/com/util/forms/PostDropdownBtn.tsx:147 msgid "You will no longer receive notifications for this thread" -msgstr "" +msgstr "Du wirst keine Mitteilungen mehr für diesen Thread erhalten" #: src/view/com/util/forms/PostDropdownBtn.tsx:150 msgid "You will now receive notifications for this thread" -msgstr "" +msgstr "Du erhälst nun Mitteilungen für dieses Thread" #: src/view/com/auth/login/SetNewPasswordForm.tsx:107 msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password." @@ -4805,21 +4519,21 @@ msgstr "Du erhältst eine E-Mail mit einem \"Reset-Code\". Gib diesen Code hier #: src/screens/Onboarding/StepModeration/index.tsx:72 msgid "You're in control" -msgstr "" +msgstr "Du hast die Kontrolle" #: src/screens/Deactivated.tsx:87 #: src/screens/Deactivated.tsx:88 #: src/screens/Deactivated.tsx:103 msgid "You're in line" -msgstr "" +msgstr "Du bist in der Warteschlange" #: src/screens/Onboarding/StepFinished.tsx:90 msgid "You're ready to go!" -msgstr "" +msgstr "Du kannst loslegen!" #: src/view/com/posts/FollowingEndOfFeed.tsx:48 msgid "You've reached the end of your feed! Find some more accounts to follow." -msgstr "" +msgstr "Du hast das Ende deines Feeds erreicht! Finde weitere Konten, denen du folgen kannst." #: src/view/com/auth/create/Step1.tsx:67 msgid "Your account" @@ -4827,11 +4541,11 @@ msgstr "Dein Konto" #: src/view/com/modals/DeleteAccount.tsx:67 msgid "Your account has been deleted" -msgstr "" +msgstr "Dein Konto wurde gelöscht" #: src/view/screens/Settings/ExportCarDialog.tsx:47 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 "" +msgstr "Dein Kontodepot, das alle öffentlichen Datensätze enthält, kann als \"CAR\"-Datei heruntergeladen werden. Diese Datei enthält keine Medieneinbettungen, wie z. B. Bilder, oder deine privaten Daten, welche separat abgerufen werden müssen." #: src/view/com/auth/create/Step1.tsx:215 msgid "Your birth date" @@ -4839,11 +4553,11 @@ msgstr "Dein Geburtsdatum" #: src/view/com/modals/InAppBrowserConsent.tsx:47 msgid "Your choice will be saved, but can be changed later in settings." -msgstr "" +msgstr "Deine Wahl wird gespeichert, kann aber später in den Einstellungen geändert werden." #: src/screens/Onboarding/StepFollowingFeed.tsx:61 msgid "Your default feed is \"Following\"" -msgstr "" +msgstr "Dein Standard-Feed ist \"Following\"" #: src/view/com/auth/create/state.ts:110 #: src/view/com/auth/login/ForgotPasswordForm.tsx:70 @@ -4851,10 +4565,6 @@ msgstr "" msgid "Your email appears to be invalid." msgstr "Deine E-Mail scheint ungültig zu sein." -#: src/view/com/modals/Waitlist.tsx:109 -#~ msgid "Your email has been saved! We'll be in touch soon." -#~ msgstr "Deine E-Mail wurde gespeichert! Wir werden uns bald bei dir melden." - #: src/view/com/modals/ChangeEmail.tsx:125 msgid "Your email has been updated but not verified. As a next step, please verify your new email." msgstr "Deine E-Mail wurde aktualisiert, aber nicht bestätigt. Als nächsten Schritt bestätige bitte deine neue E-Mail." @@ -4865,33 +4575,27 @@ msgstr "Deine E-Mail wurde noch nicht bestätigt. Dies ist ein wichtiger Sicherh #: src/view/com/posts/FollowingEmptyState.tsx:47 msgid "Your following feed is empty! Follow more users to see what's happening." -msgstr "" +msgstr "Dein Following-Feed ist leer! Folge mehr Benutzern, um auf dem Laufenden zu bleiben." #: src/view/com/auth/create/Step2.tsx:83 msgid "Your full handle will be" -msgstr "" +msgstr "Dein vollständiger Handle lautet" #: src/view/com/modals/ChangeHandle.tsx:270 msgid "Your full handle will be <0>@{0}</0>" -msgstr "" - -#: src/view/screens/Settings.tsx:430 -#: src/view/shell/desktop/RightNav.tsx:137 -#: src/view/shell/Drawer.tsx:660 -#~ msgid "Your invite codes are hidden when logged in using an App Password" -#~ msgstr "Deine Einladungscodes werden ausgeblendet, wenn du dich mit einem App-Passwort anmeldest" +msgstr "Dein vollständiger Handle lautet <0>@{0}</0>" #: src/components/dialogs/MutedWords.tsx:221 msgid "Your muted words" -msgstr "" +msgstr "Deine stummgeschalteten Wörter" #: src/view/com/modals/ChangePassword.tsx:155 msgid "Your password has been changed successfully!" -msgstr "" +msgstr "Dein Passwort wurde erfolgreich geändert!" #: src/view/com/composer/Composer.tsx:274 msgid "Your post has been published" -msgstr "" +msgstr "Dein Beitrag wurde veröffentlicht" #: src/screens/Onboarding/StepFinished.tsx:105 #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:59 @@ -4906,7 +4610,7 @@ msgstr "Dein Profil" #: src/view/com/composer/Composer.tsx:273 msgid "Your reply has been published" -msgstr "" +msgstr "Deine Antwort wurde veröffentlicht" #: src/view/com/auth/create/Step2.tsx:65 msgid "Your user handle" diff --git a/src/locale/locales/en/messages.po b/src/locale/locales/en/messages.po index c2ad626bf..b62c47ffc 100644 --- a/src/locale/locales/en/messages.po +++ b/src/locale/locales/en/messages.po @@ -4395,8 +4395,8 @@ msgstr "" msgid "Unmute thread" msgstr "" -#: src/view/screens/ProfileFeed.tsx:354 -#: src/view/screens/ProfileList.tsx:581 +#: src/view/screens/ProfileFeed.tsx:353 +#: src/view/screens/ProfileList.tsx:580 msgid "Unpin" msgstr "" @@ -4915,4 +4915,4 @@ msgstr "" #: src/view/com/auth/create/Step2.tsx:65 msgid "Your user handle" -msgstr "" +msgstr "" \ No newline at end of file diff --git a/src/locale/locales/fi/messages.po b/src/locale/locales/fi/messages.po new file mode 100644 index 000000000..f5b40f421 --- /dev/null +++ b/src/locale/locales/fi/messages.po @@ -0,0 +1,4838 @@ +msgid "" +msgstr "" +"POT-Creation-Date: 2023-11-05 16:01-0800\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: @lingui/cli\n" +"Language: fi\n" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: \n" +"Last-Translator:@jaoler.fi\n" +"Language-Team: @pekka.bsky.social,@jaoler.fi,@rahi.bsky.social\n" +"Plural-Forms: \n" + +#: src/view/com/modals/VerifyEmail.tsx:142 +msgid "(no email)" +msgstr "(ei sähköpostiosoitetta)" + +#: src/view/shell/desktop/RightNav.tsx:168 +#~ msgid "{0, plural, one {# invite code available} other {# invite codes available}}" +#~ msgstr "" + +#: src/view/com/profile/ProfileHeader.tsx:593 +msgid "{following} following" +msgstr "{following} seuraajaa" + +#: src/view/shell/desktop/RightNav.tsx:151 +#~ msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" +#~ msgstr "" + +#: src/view/screens/Settings.tsx:435 +#: src/view/shell/Drawer.tsx:664 +#~ msgid "{invitesAvailable} invite code available" +#~ msgstr "" + +#: src/view/screens/Settings.tsx:437 +#: src/view/shell/Drawer.tsx:666 +#~ msgid "{invitesAvailable} invite codes available" +#~ msgstr "" + +#: src/view/shell/Drawer.tsx:440 +msgid "{numUnreadNotifications} unread" +msgstr "{numUnreadNotifications} lukematonta" + +#: src/view/com/threadgate/WhoCanReply.tsx:158 +msgid "<0/> members" +msgstr "<0/> jäsentä" + +#: src/view/com/profile/ProfileHeader.tsx:595 +msgid "<0>{following} </0><1>following</1>" +msgstr "<0>{following} </0><1>seuraajaa</1>" + +#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:30 +msgid "<0>Choose your</0><1>Recommended</1><2>Feeds</2>" +msgstr "<0>Valitse</0><1>Suositellut</1><2>syötteet</2>" + +#: src/view/com/auth/onboarding/RecommendedFollows.tsx:37 +msgid "<0>Follow some</0><1>Recommended</1><2>Users</2>" +msgstr "<0>Seuraa joitakin</0><1>suositeltuja</1><2>käyttäjiä</2>" + +#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:21 +msgid "<0>Welcome to</0><1>Bluesky</1>" +msgstr "<0>Tervetuloa</0><1>Blueskyhin</1>" + +#: src/view/com/profile/ProfileHeader.tsx:558 +msgid "âš Invalid Handle" +msgstr "âš Virheellinen käyttäjätunnus" + +#: src/view/com/util/moderation/LabelInfo.tsx:45 +msgid "A content warning has been applied to this {0}." +msgstr "Tämä {0} sisältää sisältövaroituksen." + +#: src/lib/hooks/useOTAUpdate.ts:16 +msgid "A new version of the app is available. Please update to continue using the app." +msgstr "Sovelluksen uusi versio on saatavilla. Päivitä jatkaaksesi sovelluksen käyttöä." + +#: src/view/com/util/ViewHeader.tsx:83 +#: src/view/screens/Search/Search.tsx:647 +msgid "Access navigation links and settings" +msgstr "Siirry navigointilinkkeihin ja asetuksiin" + +#: src/view/com/home/HomeHeaderLayoutMobile.tsx:51 +msgid "Access profile and other navigation links" +msgstr "Siirry profiiliin ja muihin navigointilinkkeihin" + +#: src/view/com/modals/EditImage.tsx:299 +#: src/view/screens/Settings/index.tsx:451 +msgid "Accessibility" +msgstr "Saavutettavuus" + +#: src/view/com/auth/login/LoginForm.tsx:166 +#: src/view/screens/Settings/index.tsx:308 +#: src/view/screens/Settings/index.tsx:721 +msgid "Account" +msgstr "Tili" + +#: src/view/com/profile/ProfileHeader.tsx:246 +msgid "Account blocked" +msgstr "Tili on estetty" + +#: src/view/com/profile/ProfileHeader.tsx:213 +msgid "Account muted" +msgstr "Tili on hiljennetty" + +#: src/view/com/modals/ModerationDetails.tsx:86 +msgid "Account Muted" +msgstr "Tili on hiljennetty" + +#: src/view/com/modals/ModerationDetails.tsx:72 +msgid "Account Muted by List" +msgstr "Tili on hiljennetty listalla" + +#: src/view/com/util/AccountDropdownBtn.tsx:41 +msgid "Account options" +msgstr "Tilin asetukset" + +#: src/view/com/util/AccountDropdownBtn.tsx:25 +msgid "Account removed from quick access" +msgstr "Tili poistettu pikalinkeistä" + +#: src/view/com/profile/ProfileHeader.tsx:268 +msgid "Account unblocked" +msgstr "Tilin esto poistettu" + +#: src/view/com/profile/ProfileHeader.tsx:226 +msgid "Account unmuted" +msgstr "Tilin hiljennys poistettu" + +#: src/components/dialogs/MutedWords.tsx:147 +#: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:150 +#: src/view/com/modals/ListAddRemoveUsers.tsx:264 +#: src/view/com/modals/UserAddRemoveLists.tsx:219 +#: src/view/screens/ProfileList.tsx:812 +msgid "Add" +msgstr "Lisää" + +#: src/view/com/modals/SelfLabel.tsx:56 +msgid "Add a content warning" +msgstr "Lisää sisältövaroitus" + +#: src/view/screens/ProfileList.tsx:802 +msgid "Add a user to this list" +msgstr "Lisää käyttäjä tähän listaan" + +#: src/view/screens/Settings/index.tsx:383 +#: src/view/screens/Settings/index.tsx:392 +msgid "Add account" +msgstr "Lisää tili" + +#: src/view/com/composer/photos/Gallery.tsx:119 +#: src/view/com/composer/photos/Gallery.tsx:180 +#: src/view/com/modals/AltImage.tsx:116 +msgid "Add alt text" +msgstr "Lisää ALT-teksti" + +#: src/view/screens/AppPasswords.tsx:102 +#: src/view/screens/AppPasswords.tsx:143 +#: src/view/screens/AppPasswords.tsx:156 +msgid "Add App Password" +msgstr "Lisää sovelluksen salasana" + +#: src/view/com/modals/report/InputIssueDetails.tsx:41 +#: src/view/com/modals/report/Modal.tsx:191 +msgid "Add details" +msgstr "Lisää tiedot" + +#: src/view/com/modals/report/Modal.tsx:194 +msgid "Add details to report" +msgstr "Lisää tiedot raporttiin" + +#: src/view/com/composer/Composer.tsx:453 +msgid "Add link card" +msgstr "Lisää linkkikortti" + +#: src/view/com/composer/Composer.tsx:458 +msgid "Add link card:" +msgstr "Lisää linkkikortti:" + +#: src/components/dialogs/MutedWords.tsx:140 +msgid "Add mute word for configured settings" +msgstr "Lisää hiljennetty sana määritettyihin asetuksiin" + +#: src/components/dialogs/MutedWords.tsx:74 +msgid "Add muted words and tags" +msgstr "Lisää hiljennetyt sanat ja tunnisteet" + +#: src/view/com/modals/ChangeHandle.tsx:417 +msgid "Add the following DNS record to your domain:" +msgstr "Lisää seuraava DNS-merkintä verkkotunnukseesi:" + +#: src/view/com/profile/ProfileHeader.tsx:310 +msgid "Add to Lists" +msgstr "Lisää listoihin" + +#: src/view/com/feeds/FeedSourceCard.tsx:245 +#: src/view/screens/ProfileFeed.tsx:273 +msgid "Add to my feeds" +msgstr "Lisää syötteisiini" + +#: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:139 +msgid "Added" +msgstr "Lisätty" + +#: src/view/com/modals/ListAddRemoveUsers.tsx:191 +#: src/view/com/modals/UserAddRemoveLists.tsx:144 +msgid "Added to list" +msgstr "Lisätty listaan" + +#: src/view/com/feeds/FeedSourceCard.tsx:127 +msgid "Added to my feeds" +msgstr "Lisätty syötteisiini" + +#: src/view/screens/PreferencesFollowingFeed.tsx:173 +msgid "Adjust the number of likes a reply must have to be shown in your feed." +msgstr "Säädä, kuinka monta tykkäystä vastauksen on saatava näkyäkseen syötteessäsi." + +#: src/view/com/modals/SelfLabel.tsx:75 +msgid "Adult Content" +msgstr "Aikuissisältöä" + +#: src/view/com/modals/ContentFilteringSettings.tsx:141 +msgid "Adult content can only be enabled via the Web at <0/>." +msgstr "Aikuissisältö voidaan ottaa käyttöön vain verkon kautta osoitteessa <0/>." + +#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:78 +#~ msgid "Adult content can only be enabled via the Web at <0>bsky.app</0>." +#~ msgstr "" + +#: src/view/screens/Settings/index.tsx:664 +msgid "Advanced" +msgstr "Edistynyt" + +#: src/view/screens/Feeds.tsx:666 +msgid "All the feeds you've saved, right in one place." +msgstr "Kaikki tallentamasi syötteet yhdessä paikassa." + +#: src/view/com/auth/login/ForgotPasswordForm.tsx:221 +#: src/view/com/modals/ChangePassword.tsx:168 +msgid "Already have a code?" +msgstr "Onko sinulla jo koodi?" + +#: src/view/com/auth/login/ChooseAccountForm.tsx:98 +msgid "Already signed in as @{0}" +msgstr "Kirjautuneena sisään nimellä @{0}" + +#: src/view/com/composer/photos/Gallery.tsx:130 +msgid "ALT" +msgstr "ALT" + +#: src/view/com/modals/EditImage.tsx:315 +msgid "Alt text" +msgstr "ALT-teksti" + +#: src/view/com/composer/photos/Gallery.tsx:209 +msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone." +msgstr "ALT-teksti kuvailee kuvia sokeille ja heikkonäköisille käyttäjille sekä lisää kontekstia kaikille." + +#: src/view/com/modals/VerifyEmail.tsx:124 +msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below." +msgstr "Sähköposti on lähetetty osoitteeseen {0}. Siinä on vahvistuskoodi, jonka voit syöttää alla." + +#: src/view/com/modals/ChangeEmail.tsx:119 +msgid "An email has been sent to your previous address, {0}. It includes a confirmation code which you can enter below." +msgstr "Sähköposti on lähetetty aiempaan osoitteeseesi, {0}. Siinä on vahvistuskoodi, jonka voit syöttää alla." + +#: src/view/com/profile/FollowButton.tsx:30 +#: src/view/com/profile/FollowButton.tsx:40 +msgid "An issue occurred, please try again." +msgstr "Tapahtui virhe, yritä uudelleen." + +#: src/view/com/notifications/FeedItem.tsx:236 +#: src/view/com/threadgate/WhoCanReply.tsx:178 +msgid "and" +msgstr "ja" + +#: src/screens/Onboarding/index.tsx:32 +msgid "Animals" +msgstr "Eläimet" + +#: src/view/screens/LanguageSettings.tsx:95 +msgid "App Language" +msgstr "Sovelluksen kieli" + +#: src/view/screens/AppPasswords.tsx:228 +msgid "App password deleted" +msgstr "Sovelluksen salasana poistettu" + +#: src/view/com/modals/AddAppPasswords.tsx:134 +msgid "App Password names can only contain letters, numbers, spaces, dashes, and underscores." +msgstr "Sovelluksen salasanan nimet voivat sisältää vain kirjaimia, numeroita, välilyöntejä, viivoja ja alaviivoja." + +#: src/view/com/modals/AddAppPasswords.tsx:99 +msgid "App Password names must be at least 4 characters long." +msgstr "Sovelluksen salasanojen nimien on oltava vähintään 4 merkkiä pitkiä." + +#: src/view/screens/Settings/index.tsx:675 +msgid "App password settings" +msgstr "Sovelluksen salasanan asetukset" + +#: src/view/screens/Settings.tsx:650 +#~ msgid "App passwords" +#~ msgstr "" + +#: src/Navigation.tsx:237 +#: src/view/screens/AppPasswords.tsx:187 +#: src/view/screens/Settings/index.tsx:684 +msgid "App Passwords" +msgstr "Sovellussalasanat" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:295 +msgid "Appeal content warning" +msgstr "Valita sisältövaroituksesta" + +#: src/view/com/modals/AppealLabel.tsx:65 +msgid "Appeal Content Warning" +msgstr "Valita sisältövaroituksesta" + +#: src/view/com/util/moderation/LabelInfo.tsx:52 +msgid "Appeal this decision" +msgstr "Valita tästä päätöksestä" + +#: src/view/com/util/moderation/LabelInfo.tsx:56 +msgid "Appeal this decision." +msgstr "Valita tästä päätöksestä." + +#: src/view/screens/Settings/index.tsx:466 +msgid "Appearance" +msgstr "Ulkonäkö" + +#: src/view/screens/AppPasswords.tsx:224 +msgid "Are you sure you want to delete the app password \"{name}\"?" +msgstr "Haluatko varmasti poistaa sovellussalasanan \"{name}\"?" + +#: src/view/com/composer/Composer.tsx:150 +msgid "Are you sure you'd like to discard this draft?" +msgstr "Haluatko varmasti hylätä tämän luonnoksen?" + +#: src/components/dialogs/MutedWords.tsx:233 +#: src/view/screens/ProfileList.tsx:365 +msgid "Are you sure?" +msgstr "Oletko varma?" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:278 +msgid "Are you sure? This cannot be undone." +msgstr "Oletko varma? Tätä ei voi perua." + +#: src/view/com/composer/select-language/SuggestedLanguage.tsx:60 +msgid "Are you writing in <0>{0}</0>?" +msgstr "Onko viestisi kieli <0>{0}</0>?" + +#: src/screens/Onboarding/index.tsx:26 +msgid "Art" +msgstr "Taide" + +#: src/view/com/modals/SelfLabel.tsx:123 +msgid "Artistic or non-erotic nudity." +msgstr "Taiteellinen tai ei-eroottinen alastomuus." + +#: src/view/com/auth/create/CreateAccount.tsx:158 +#: src/view/com/auth/login/ChooseAccountForm.tsx:151 +#: src/view/com/auth/login/ForgotPasswordForm.tsx:174 +#: src/view/com/auth/login/LoginForm.tsx:259 +#: src/view/com/auth/login/SetNewPasswordForm.tsx:179 +#: src/view/com/modals/report/InputIssueDetails.tsx:46 +#: src/view/com/post-thread/PostThread.tsx:472 +#: src/view/com/post-thread/PostThread.tsx:522 +#: src/view/com/post-thread/PostThread.tsx:530 +#: src/view/com/profile/ProfileHeader.tsx:649 +#: src/view/com/util/ViewHeader.tsx:81 +msgid "Back" +msgstr "Takaisin" + +#: src/view/com/post-thread/PostThread.tsx:480 +msgctxt "action" +msgid "Back" +msgstr "Takaisin" + +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:136 +msgid "Based on your interest in {interestsText}" +msgstr "Perustuen kiinnostukseesi {interestsText}" + +#: src/view/screens/Settings/index.tsx:523 +msgid "Basics" +msgstr "Perusasiat" + +#: src/view/com/auth/create/Step1.tsx:250 +#: src/view/com/modals/BirthDateSettings.tsx:73 +msgid "Birthday" +msgstr "Syntymäpäivä" + +#: src/view/screens/Settings/index.tsx:340 +msgid "Birthday:" +msgstr "Syntymäpäivä:" + +#: src/view/com/profile/ProfileHeader.tsx:239 +#: src/view/com/profile/ProfileHeader.tsx:346 +msgid "Block Account" +msgstr "Estä käyttäjä" + +#: src/view/screens/ProfileList.tsx:556 +msgid "Block accounts" +msgstr "Estä käyttäjät" + +#: src/view/screens/ProfileList.tsx:506 +msgid "Block list" +msgstr "Estettyjen lista" + +#: src/view/screens/ProfileList.tsx:316 +msgid "Block these accounts?" +msgstr "Estetäänkö nämä käyttäjät?" + +#: src/view/screens/ProfileList.tsx:320 +msgid "Block this List" +msgstr "Estä tämä lista" + +#: src/view/com/lists/ListCard.tsx:110 +#: src/view/com/util/post-embeds/QuoteEmbed.tsx:61 +msgid "Blocked" +msgstr "Estetty" + +#: src/view/screens/Moderation.tsx:142 +msgid "Blocked accounts" +msgstr "Estetyt käyttäjät" + +#: src/Navigation.tsx:130 +#: src/view/screens/ModerationBlockedAccounts.tsx:107 +msgid "Blocked Accounts" +msgstr "Estetyt käyttäjät" + +#: src/view/com/profile/ProfileHeader.tsx:241 +msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." +msgstr "Estetyt käyttäjät eivät voi vastata viesteihisi, mainita sinua tai muuten olla vuorovaikutuksessa kanssasi." + +#: src/view/screens/ModerationBlockedAccounts.tsx:115 +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 "Estetyt käyttäjät eivät voi vastata viesteihisi, mainita sinua tai muuten olla vuorovaikutuksessa kanssasi. Et näe heidän sisältöään ja he eivät näe sinun sisältöäsi." + +#: src/view/com/post-thread/PostThread.tsx:324 +msgid "Blocked post." +msgstr "Estetty viesti." + +#: src/view/screens/ProfileList.tsx:318 +msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." +msgstr "Estäminen on julkista. Estetyt käyttäjät eivät voi vastata viesteihisi, mainita sinua tai muuten olla vuorovaikutuksessa kanssasi." + +#: src/view/com/auth/HomeLoggedOutCTA.tsx:93 +#: src/view/com/auth/SplashScreen.web.tsx:133 +msgid "Blog" +msgstr "Blogi" + +#: src/view/com/auth/HomeLoggedOutCTA.tsx:31 +#: src/view/com/auth/server-input/index.tsx:89 +#: src/view/com/auth/server-input/index.tsx:90 +msgid "Bluesky" +msgstr "Bluesky" + +#: src/view/com/auth/server-input/index.tsx:150 +msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers." +msgstr "Bluesky on avoin verkko, jossa voit valita palveluntarjoajasi. Räätälöity palveluntarjoajan määritys on nyt saatavilla betavaiheen kehittäjille." + +#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80 +#: src/view/com/auth/onboarding/WelcomeMobile.tsx:80 +msgid "Bluesky is flexible." +msgstr "Bluesky on joustava." + +#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:69 +#: src/view/com/auth/onboarding/WelcomeMobile.tsx:69 +msgid "Bluesky is open." +msgstr "Bluesky on avoin." + +#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:56 +#: src/view/com/auth/onboarding/WelcomeMobile.tsx:56 +msgid "Bluesky is public." +msgstr "Bluesky on julkinen." + +#: src/view/com/modals/Waitlist.tsx:70 +msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon." +msgstr "Bluesky käyttää kutsuja rakentaakseen terveellisemmän yhteisön. Jos et tunne ketään, jolla on kutsu, voit ilmoittautua odotuslistalle, niin lähetämme sinulle pian yhden." + +#: src/view/screens/Moderation.tsx:245 +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 ei näytä profiiliasi ja viestejäsi kirjautumattomille käyttäjille. Toiset sovellukset eivät ehkä noudata tätä asetusta. Tämä ei tee tilistäsi yksityistä." + +#: src/view/com/modals/ServerInput.tsx:78 +#~ msgid "Bluesky.Social" +#~ msgstr "" + +#: src/screens/Onboarding/index.tsx:33 +msgid "Books" +msgstr "Kirjat" + +#: src/view/screens/Settings/index.tsx:859 +msgid "Build version {0} {1}" +msgstr "Versio {0} {1}" + +#: src/view/com/auth/HomeLoggedOutCTA.tsx:87 +#: src/view/com/auth/SplashScreen.web.tsx:128 +msgid "Business" +msgstr "Yritys" + +#: src/view/com/modals/ServerInput.tsx:115 +#~ msgid "Button disabled. Input custom domain to proceed." +#~ msgstr "" + +#: src/view/com/profile/ProfileSubpageHeader.tsx:157 +msgid "by —" +msgstr "käyttäjä —" + +#: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:100 +msgid "by {0}" +msgstr "käyttäjältä {0}" + +#: src/view/com/profile/ProfileSubpageHeader.tsx:161 +msgid "by <0/>" +msgstr "käyttäjältä <0/>" + +#: src/view/com/profile/ProfileSubpageHeader.tsx:159 +msgid "by you" +msgstr "sinulta" + +#: src/view/com/composer/photos/OpenCameraBtn.tsx:60 +#: src/view/com/util/UserAvatar.tsx:224 +#: src/view/com/util/UserBanner.tsx:40 +msgid "Camera" +msgstr "Kamera" + +#: src/view/com/modals/AddAppPasswords.tsx:216 +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 "Voi sisältää vain kirjaimia, numeroita, välilyöntejä, viivoja ja alaviivoja. Täytyy olla vähintään 4 merkkiä pitkä, mutta enintään 32 merkkiä pitkä." + +#: src/components/Prompt.tsx:91 +#: src/view/com/composer/Composer.tsx:307 +#: src/view/com/composer/Composer.tsx:312 +#: src/view/com/modals/ChangeEmail.tsx:218 +#: src/view/com/modals/ChangeEmail.tsx:220 +#: src/view/com/modals/ChangePassword.tsx:265 +#: src/view/com/modals/ChangePassword.tsx:268 +#: src/view/com/modals/CreateOrEditList.tsx:355 +#: src/view/com/modals/EditImage.tsx:323 +#: src/view/com/modals/EditProfile.tsx:249 +#: src/view/com/modals/InAppBrowserConsent.tsx:78 +#: src/view/com/modals/LinkWarning.tsx:87 +#: src/view/com/modals/Repost.tsx:87 +#: src/view/com/modals/VerifyEmail.tsx:247 +#: src/view/com/modals/VerifyEmail.tsx:253 +#: src/view/com/modals/Waitlist.tsx:142 +#: src/view/screens/Search/Search.tsx:693 +#: src/view/shell/desktop/Search.tsx:238 +msgid "Cancel" +msgstr "Peruuta" + +#: src/view/com/modals/Confirm.tsx:88 +#: src/view/com/modals/Confirm.tsx:91 +#: src/view/com/modals/CreateOrEditList.tsx:360 +#: src/view/com/modals/DeleteAccount.tsx:156 +#: src/view/com/modals/DeleteAccount.tsx:234 +msgctxt "action" +msgid "Cancel" +msgstr "Peruuta" + +#: src/view/com/modals/DeleteAccount.tsx:152 +#: src/view/com/modals/DeleteAccount.tsx:230 +msgid "Cancel account deletion" +msgstr "Peruuta tilin poisto" + +#: src/view/com/modals/ChangeHandle.tsx:149 +msgid "Cancel change handle" +msgstr "Peruuta käyttäjätunnuksen vaihto" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:134 +msgid "Cancel image crop" +msgstr "Peruuta kuvan rajaus" + +#: src/view/com/modals/EditProfile.tsx:244 +msgid "Cancel profile editing" +msgstr "Peruuta profiilin muokkaus" + +#: src/view/com/modals/Repost.tsx:78 +msgid "Cancel quote post" +msgstr "Peruuta uudelleenpostaus" + +#: src/view/com/modals/ListAddRemoveUsers.tsx:87 +#: src/view/shell/desktop/Search.tsx:234 +msgid "Cancel search" +msgstr "Peruuta haku" + +#: src/view/com/modals/Waitlist.tsx:136 +msgid "Cancel waitlist signup" +msgstr "Peruuta odotuslistalle liittyminen" + +#: src/view/screens/Settings/index.tsx:334 +msgctxt "action" +msgid "Change" +msgstr "Vaihda" + +#: src/view/screens/Settings/index.tsx:696 +msgid "Change handle" +msgstr "Vaihda käyttäjätunnus" + +#: src/view/com/modals/ChangeHandle.tsx:161 +#: src/view/screens/Settings/index.tsx:705 +msgid "Change Handle" +msgstr "Vaihda käyttäjätunnus" + +#: src/view/com/modals/VerifyEmail.tsx:147 +msgid "Change my email" +msgstr "Vaihda sähköpostiosoitteeni" + +#: src/view/screens/Settings/index.tsx:732 +msgid "Change password" +msgstr "Vaihda salasana" + +#: src/view/screens/Settings/index.tsx:741 +msgid "Change Password" +msgstr "Vaihda salasana" + +#: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 +msgid "Change post language to {0}" +msgstr "Vaihda julkaisun kieleksi {0}" + +#: src/view/screens/Settings/index.tsx:733 +msgid "Change your Bluesky password" +msgstr "Vaihda Bluesky-salasanasi" + +#: src/view/com/modals/ChangeEmail.tsx:109 +msgid "Change Your Email" +msgstr "Vaihda sähköpostiosoitteesi" + +#: src/screens/Deactivated.tsx:72 +#: src/screens/Deactivated.tsx:76 +msgid "Check my status" +msgstr "Tarkista tilani" + +#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:121 +msgid "Check out some recommended feeds. Tap + to add them to your list of pinned feeds." +msgstr "Katso joitakin suositeltuja syötteitä. Napauta + lisätäksesi ne kiinnitettyjen syötteiden luetteloon." + +#: src/view/com/auth/onboarding/RecommendedFollows.tsx:185 +msgid "Check out some recommended users. Follow them to see similar users." +msgstr "Tutustu suositeltuihin käyttäjiin. Seuraa heitä löytääksesi samankaltaisia käyttäjiä." + +#: src/view/com/modals/DeleteAccount.tsx:169 +msgid "Check your inbox for an email with the confirmation code to enter below:" +msgstr "Tarkista sähköpostisi ja syötä saamasi vahvistuskoodi alle:" + +#: src/view/com/modals/Threadgate.tsx:72 +msgid "Choose \"Everybody\" or \"Nobody\"" +msgstr "Valitse \"Kaikki\" tai \"Ei kukaan\"" + +#: src/view/screens/Settings/index.tsx:697 +msgid "Choose a new Bluesky username or create" +msgstr "Valitse uusi Bluesky-käyttäjätunnus tai luo" + +#: src/view/com/auth/server-input/index.tsx:79 +msgid "Choose Service" +msgstr "Valitse palvelu" + +#: src/screens/Onboarding/StepFinished.tsx:135 +msgid "Choose the algorithms that power your custom feeds." +msgstr "Valitse algoritmit, jotka ohjaavat mukautettuja syötteitäsi." + +#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:83 +#: src/view/com/auth/onboarding/WelcomeMobile.tsx:83 +msgid "Choose the algorithms that power your experience with custom feeds." +msgstr "Valitse algoritmit, jotka ohjaavat kokemustasi mukautettujen syötteiden kanssa." + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 +#~ msgid "Choose your algorithmic feeds" +#~ msgstr "" + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 +msgid "Choose your main feeds" +msgstr "Valitse pääsyötteet" + +#: src/view/com/auth/create/Step1.tsx:219 +msgid "Choose your password" +msgstr "Valitse salasanasi" + +#: src/view/screens/Settings/index.tsx:834 +#: src/view/screens/Settings/index.tsx:835 +msgid "Clear all legacy storage data" +msgstr "Tyhjennä kaikki vanhan tietomallin mukaiset tiedot" + +#: src/view/screens/Settings/index.tsx:837 +msgid "Clear all legacy storage data (restart after this)" +msgstr "Tyhjennä kaikki vanhan tietomallin tiedot (käynnistä uudelleen tämän jälkeen)" + +#: src/view/screens/Settings/index.tsx:846 +#: src/view/screens/Settings/index.tsx:847 +msgid "Clear all storage data" +msgstr "Tyhjennä kaikki tallennukset" + +#: src/view/screens/Settings/index.tsx:849 +msgid "Clear all storage data (restart after this)" +msgstr "Tyhjennä kaikki tallennukset (käynnistä uudelleen tämän jälkeen)" + +#: src/view/com/util/forms/SearchInput.tsx:88 +#: src/view/screens/Search/Search.tsx:697 +msgid "Clear search query" +msgstr "Tyhjennä hakukysely" + +#: src/view/screens/Support.tsx:40 +msgid "click here" +msgstr "klikkaa tästä" + +#: src/components/RichText.tsx:189 +#: src/components/TagMenu/index.web.tsx:125 +msgid "Click here to open tag menu for {tag}" +msgstr "Avaa tästä valikko tunnisteelle {tag}" + +#: src/screens/Onboarding/index.tsx:35 +msgid "Climate" +msgstr "Ilmasto" + +#: src/view/com/modals/ChangePassword.tsx:265 +#: src/view/com/modals/ChangePassword.tsx:268 +msgid "Close" +msgstr "Sulje" + +#: src/components/Dialog/index.web.tsx:80 +#: src/components/Dialog/index.web.tsx:194 +msgid "Close active dialog" +msgstr "Sulje aktiivinen ikkuna" + +#: src/view/com/auth/login/PasswordUpdatedForm.tsx:38 +msgid "Close alert" +msgstr "Sulje hälytys" + +#: src/view/com/util/BottomSheetCustomBackdrop.tsx:33 +msgid "Close bottom drawer" +msgstr "Sulje alavalinnat" + +#: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:26 +msgid "Close image" +msgstr "Sulje kuva" + +#: src/view/com/lightbox/Lightbox.web.tsx:119 +msgid "Close image viewer" +msgstr "Sulje kuvankatselu" + +#: src/view/shell/index.web.tsx:51 +msgid "Close navigation footer" +msgstr "Sulje alanavigointi" + +#: src/components/TagMenu/index.tsx:266 +msgid "Close this dialog" +msgstr "Sulje tämä valintaikkuna" + +#: src/view/shell/index.web.tsx:52 +msgid "Closes bottom navigation bar" +msgstr "Sulkee alanavigaation" + +#: src/view/com/auth/login/PasswordUpdatedForm.tsx:39 +msgid "Closes password update alert" +msgstr "Sulkee salasanan päivitysilmoituksen" + +#: src/view/com/composer/Composer.tsx:309 +msgid "Closes post composer and discards post draft" +msgstr "Sulkee editorin ja hylkää luonnoksen" + +#: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:27 +msgid "Closes viewer for header image" +msgstr "Sulkee kuvan katseluohjelman" + +#: src/view/com/notifications/FeedItem.tsx:317 +msgid "Collapses list of users for a given notification" +msgstr "Pienentää käyttäjäluettelon annetulle ilmoitukselle" + +#: src/screens/Onboarding/index.tsx:41 +msgid "Comedy" +msgstr "Komedia" + +#: src/screens/Onboarding/index.tsx:27 +msgid "Comics" +msgstr "Sarjakuvat" + +#: src/Navigation.tsx:227 +#: src/view/screens/CommunityGuidelines.tsx:32 +msgid "Community Guidelines" +msgstr "Yhteisöohjeet" + +#: src/screens/Onboarding/StepFinished.tsx:148 +msgid "Complete onboarding and start using your account" +msgstr "Suorita käyttöönotto loppuun ja aloita tilisi käyttö" + +#: src/view/com/auth/create/Step3.tsx:73 +msgid "Complete the challenge" +msgstr "Tee haaste loppuun" + +#: src/view/com/composer/Composer.tsx:424 +msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" +msgstr "Laadi viestejä, joiden pituus on enintään {MAX_GRAPHEME_LENGTH} merkkiä" + +#: src/view/com/composer/Prompt.tsx:24 +msgid "Compose reply" +msgstr "Kirjoita vastaus" + +#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:67 +msgid "Configure content filtering setting for category: {0}" +msgstr "Määritä sisällönsuodatusasetus aiheille: {0}" + +#: src/components/Prompt.tsx:113 +#: src/view/com/modals/AppealLabel.tsx:98 +#: src/view/com/modals/SelfLabel.tsx:154 +#: src/view/com/modals/VerifyEmail.tsx:231 +#: src/view/com/modals/VerifyEmail.tsx:233 +#: src/view/screens/PreferencesFollowingFeed.tsx:308 +#: src/view/screens/PreferencesThreads.tsx:159 +msgid "Confirm" +msgstr "Vahvista" + +#: src/view/com/modals/Confirm.tsx:75 +#: src/view/com/modals/Confirm.tsx:78 +msgctxt "action" +msgid "Confirm" +msgstr "Vahvista" + +#: src/view/com/modals/ChangeEmail.tsx:193 +#: src/view/com/modals/ChangeEmail.tsx:195 +msgid "Confirm Change" +msgstr "Vahvista muutos" + +#: src/view/com/modals/lang-settings/ConfirmLanguagesButton.tsx:34 +msgid "Confirm content language settings" +msgstr "Vahvista sisällön kieliasetukset" + +#: src/view/com/modals/DeleteAccount.tsx:220 +msgid "Confirm delete account" +msgstr "Vahvista tilin poisto" + +#: src/view/com/modals/ContentFilteringSettings.tsx:156 +msgid "Confirm your age to enable adult content." +msgstr "Vahvista ikäsi nähdäksesi ikärajarajoitettua sisältöä" + +#: src/view/com/modals/ChangeEmail.tsx:157 +#: src/view/com/modals/DeleteAccount.tsx:182 +#: src/view/com/modals/VerifyEmail.tsx:165 +msgid "Confirmation code" +msgstr "Vahvistuskoodi" + +#: src/view/com/modals/Waitlist.tsx:120 +msgid "Confirms signing up {email} to the waitlist" +msgstr "Vahvistaa sähköpostiosoitteen {email} - rekisteröinnin odotuslistalle" + +#: src/view/com/auth/create/CreateAccount.tsx:193 +#: src/view/com/auth/login/LoginForm.tsx:278 +msgid "Connecting..." +msgstr "Yhdistetään..." + +#: src/view/com/auth/create/CreateAccount.tsx:213 +msgid "Contact support" +msgstr "Ota yhteyttä tukeen" + +#: src/view/screens/Moderation.tsx:83 +msgid "Content filtering" +msgstr "Sisällönsuodatus" + +#: src/view/com/modals/ContentFilteringSettings.tsx:44 +msgid "Content Filtering" +msgstr "Sisällönsuodatus" + +#: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:74 +#: src/view/screens/LanguageSettings.tsx:278 +msgid "Content Languages" +msgstr "Sisältöjen kielet" + +#: src/view/com/modals/ModerationDetails.tsx:65 +msgid "Content Not Available" +msgstr "Sisältö ei ole saatavilla" + +#: src/view/com/modals/ModerationDetails.tsx:33 +#: src/view/com/util/moderation/ScreenHider.tsx:78 +msgid "Content Warning" +msgstr "Sisältövaroitus" + +#: src/view/com/composer/labels/LabelsBtn.tsx:31 +msgid "Content warnings" +msgstr "Sisältövaroitukset" + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:170 +#: src/screens/Onboarding/StepFollowingFeed.tsx:153 +#: src/screens/Onboarding/StepInterests/index.tsx:248 +#: src/screens/Onboarding/StepModeration/index.tsx:118 +#: src/screens/Onboarding/StepTopicalFeeds.tsx:114 +#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:148 +#: src/view/com/auth/onboarding/RecommendedFollows.tsx:209 +msgid "Continue" +msgstr "Jatka" + +#: src/screens/Onboarding/StepFollowingFeed.tsx:150 +#: src/screens/Onboarding/StepInterests/index.tsx:245 +#: src/screens/Onboarding/StepModeration/index.tsx:115 +#: src/screens/Onboarding/StepTopicalFeeds.tsx:111 +msgid "Continue to next step" +msgstr "Jatka seuraavaan vaiheeseen" + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:167 +msgid "Continue to the next step" +msgstr "Jatka seuraavaan vaiheeseen" + +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:191 +msgid "Continue to the next step without following any accounts" +msgstr "Jatka seuraavaan vaiheeseen seuraamatta yhtään tiliä" + +#: src/screens/Onboarding/index.tsx:44 +msgid "Cooking" +msgstr "Ruoanlaitto" + +#: src/view/com/modals/AddAppPasswords.tsx:195 +#: src/view/com/modals/InviteCodes.tsx:182 +msgid "Copied" +msgstr "Kopioitu" + +#: src/view/screens/Settings/index.tsx:241 +msgid "Copied build version to clipboard" +msgstr "Ohjelmiston versio kopioitu leikepöydälle" + +#: src/view/com/modals/AddAppPasswords.tsx:76 +#: src/view/com/modals/InviteCodes.tsx:152 +#: src/view/com/util/forms/PostDropdownBtn.tsx:143 +msgid "Copied to clipboard" +msgstr "Kopioitu leikepöydälle" + +#: src/view/com/modals/AddAppPasswords.tsx:189 +msgid "Copies app password" +msgstr "Kopioi sovellussalasanan" + +#: src/view/com/modals/AddAppPasswords.tsx:188 +msgid "Copy" +msgstr "Kopioi" + +#: src/view/screens/ProfileList.tsx:418 +msgid "Copy link to list" +msgstr "Kopioi listan linkki" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:184 +msgid "Copy link to post" +msgstr "Kopioi julkaisun linkki" + +#: src/view/com/profile/ProfileHeader.tsx:295 +msgid "Copy link to profile" +msgstr "Kopioi linkki profiiliin" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:170 +msgid "Copy post text" +msgstr "Kopioi viestin teksti" + +#: src/Navigation.tsx:232 +#: src/view/screens/CopyrightPolicy.tsx:29 +msgid "Copyright Policy" +msgstr "Tekijänoikeuskäytäntö" + +#: src/view/screens/ProfileFeed.tsx:97 +msgid "Could not load feed" +msgstr "Syötettä ei voitu ladata" + +#: src/view/screens/ProfileList.tsx:888 +msgid "Could not load list" +msgstr "Listaa ei voitu ladata" + +#: src/view/com/auth/create/Step2.tsx:91 +#~ msgid "Country" +#~ msgstr "Maa" + +#: src/view/com/auth/HomeLoggedOutCTA.tsx:62 +#: src/view/com/auth/SplashScreen.tsx:71 +#: src/view/com/auth/SplashScreen.web.tsx:81 +msgid "Create a new account" +msgstr "Luo uusi tili" + +#: src/view/screens/Settings/index.tsx:384 +msgid "Create a new Bluesky account" +msgstr "Luo uusi Bluesky-tili" + +#: src/view/com/auth/create/CreateAccount.tsx:133 +msgid "Create Account" +msgstr "Luo tili" + +#: src/view/com/modals/AddAppPasswords.tsx:226 +msgid "Create App Password" +msgstr "Luo sovellussalasana" + +#: src/view/com/auth/HomeLoggedOutCTA.tsx:54 +#: src/view/com/auth/SplashScreen.tsx:68 +msgid "Create new account" +msgstr "Luo uusi tili" + +#: src/view/screens/AppPasswords.tsx:249 +msgid "Created {0}" +msgstr "{0} luotu" + +#: src/view/screens/ProfileFeed.tsx:616 +msgid "Created by <0/>" +msgstr "Luonut <0/>" + +#: src/view/screens/ProfileFeed.tsx:614 +msgid "Created by you" +msgstr "Sinun luoma sisältö" + +#: src/view/com/composer/Composer.tsx:455 +msgid "Creates a card with a thumbnail. The card links to {url}" +msgstr "Luo kortin pikkukuvan kanssa. Kortti linkittyy osoitteeseen {url}" + +#: src/screens/Onboarding/index.tsx:29 +msgid "Culture" +msgstr "Kulttuuri" + +#: src/view/com/auth/server-input/index.tsx:95 +#: src/view/com/auth/server-input/index.tsx:96 +msgid "Custom" +msgstr "Mukautettu" + +#: src/view/com/modals/ChangeHandle.tsx:389 +msgid "Custom domain" +msgstr "Mukautettu verkkotunnus" + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 +#: src/view/screens/Feeds.tsx:692 +msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." +msgstr "Yhteisön rakentamat mukautetut syötteet tuovat sinulle uusia kokemuksia ja auttavat löytämään sinulle mieluisaa sisältöä." + +#: src/view/screens/PreferencesExternalEmbeds.tsx:55 +msgid "Customize media from external sites." +msgstr "Muokkaa mediaa ulkoisista sivustoista." + +#: src/view/screens/Settings.tsx:687 +#~ msgid "Danger Zone" +#~ msgstr "" + +#: src/view/screens/Settings/index.tsx:485 +#: src/view/screens/Settings/index.tsx:511 +msgid "Dark" +msgstr "Tumma" + +#: src/view/screens/Debug.tsx:63 +msgid "Dark mode" +msgstr "Tumma ulkoasu" + +#: src/view/screens/Settings/index.tsx:498 +msgid "Dark Theme" +msgstr "Tumma teema" + +#: src/view/screens/Debug.tsx:83 +msgid "Debug panel" +msgstr "Vianetsintäpaneeli" + +#: src/view/screens/Settings/index.tsx:772 +msgid "Delete account" +msgstr "Poista käyttäjätili" + +#: src/view/com/modals/DeleteAccount.tsx:87 +msgid "Delete Account" +msgstr "Poista käyttäjätili" + +#: src/view/screens/AppPasswords.tsx:222 +#: src/view/screens/AppPasswords.tsx:242 +msgid "Delete app password" +msgstr "Poista sovellussalasana" + +#: src/view/screens/ProfileList.tsx:364 +#: src/view/screens/ProfileList.tsx:445 +msgid "Delete List" +msgstr "Poista lista" + +#: src/view/com/modals/DeleteAccount.tsx:223 +msgid "Delete my account" +msgstr "Poista käyttäjätilini" + +#: src/view/screens/Settings.tsx:706 +#~ msgid "Delete my account…" +#~ msgstr "" + +#: src/view/screens/Settings/index.tsx:784 +msgid "Delete My Account…" +msgstr "Poista tilini…" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:273 +msgid "Delete post" +msgstr "Poista viesti" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:277 +msgid "Delete this post?" +msgstr "Poista tämä viesti?" + +#: src/view/com/util/post-embeds/QuoteEmbed.tsx:70 +msgid "Deleted" +msgstr "Poistettu" + +#: src/view/com/post-thread/PostThread.tsx:316 +msgid "Deleted post." +msgstr "Poistettu viesti." + +#: src/view/com/modals/CreateOrEditList.tsx:300 +#: src/view/com/modals/CreateOrEditList.tsx:321 +#: src/view/com/modals/EditProfile.tsx:198 +#: src/view/com/modals/EditProfile.tsx:210 +msgid "Description" +msgstr "Kuvaus" + +#: src/view/screens/Settings.tsx:760 +#~ msgid "Developer Tools" +#~ msgstr "" + +#: src/view/com/composer/Composer.tsx:218 +msgid "Did you want to say anything?" +msgstr "Haluatko sanoa jotain?" + +#: src/view/screens/Settings/index.tsx:504 +msgid "Dim" +msgstr "Himmeä" + +#: src/view/com/composer/Composer.tsx:151 +msgid "Discard" +msgstr "Hylkää" + +#: src/view/com/composer/Composer.tsx:145 +msgid "Discard draft" +msgstr "Hylkää luonnos" + +#: src/view/screens/Moderation.tsx:226 +msgid "Discourage apps from showing my account to logged-out users" +msgstr "Estä sovelluksia näyttämästä tiliäni kirjautumattomille käyttäjille" + +#: src/view/com/posts/FollowingEmptyState.tsx:74 +#: src/view/com/posts/FollowingEndOfFeed.tsx:75 +msgid "Discover new custom feeds" +msgstr "Löydä uusia mukautettuja syötteitä" + +#: src/view/screens/Feeds.tsx:473 +#~ msgid "Discover new feeds" +#~ msgstr "" + +#: src/view/screens/Feeds.tsx:689 +msgid "Discover New Feeds" +msgstr "Löydä uusia syötteitä" + +#: src/view/com/modals/EditProfile.tsx:192 +msgid "Display name" +msgstr "Näyttönimi" + +#: src/view/com/modals/EditProfile.tsx:180 +msgid "Display Name" +msgstr "Näyttönimi" + +#: src/view/com/modals/ChangeHandle.tsx:487 +msgid "Domain verified!" +msgstr "Verkkotunnus vahvistettu!" + +#: src/view/com/auth/create/Step1.tsx:170 +msgid "Don't have an invite code?" +msgstr "Eikö sinulla ole kutsukoodia?" + +#: src/view/com/auth/onboarding/RecommendedFollows.tsx:86 +#: src/view/com/modals/EditImage.tsx:333 +#: src/view/com/modals/ListAddRemoveUsers.tsx:144 +#: src/view/com/modals/SelfLabel.tsx:157 +#: src/view/com/modals/Threadgate.tsx:129 +#: src/view/com/modals/Threadgate.tsx:132 +#: src/view/com/modals/UserAddRemoveLists.tsx:95 +#: src/view/com/modals/UserAddRemoveLists.tsx:98 +#: src/view/screens/PreferencesThreads.tsx:162 +msgctxt "action" +msgid "Done" +msgstr "Valmis" + +#: src/view/com/auth/server-input/index.tsx:165 +#: src/view/com/auth/server-input/index.tsx:166 +#: src/view/com/modals/AddAppPasswords.tsx:226 +#: src/view/com/modals/AltImage.tsx:139 +#: src/view/com/modals/ContentFilteringSettings.tsx:88 +#: src/view/com/modals/ContentFilteringSettings.tsx:96 +#: src/view/com/modals/crop-image/CropImage.web.tsx:152 +#: src/view/com/modals/InviteCodes.tsx:80 +#: src/view/com/modals/InviteCodes.tsx:123 +#: src/view/com/modals/ListAddRemoveUsers.tsx:142 +#: src/view/screens/PreferencesFollowingFeed.tsx:311 +#: src/view/screens/Settings/ExportCarDialog.tsx:93 +#: src/view/screens/Settings/ExportCarDialog.tsx:94 +msgid "Done" +msgstr "Valmis" + +#: src/view/com/modals/lang-settings/ConfirmLanguagesButton.tsx:42 +msgid "Done{extraText}" +msgstr "Valmis{extraText}" + +#: src/view/com/auth/login/ChooseAccountForm.tsx:45 +msgid "Double tap to sign in" +msgstr "Kaksoisnapauta kirjautuaksesi sisään" + +#: src/view/screens/Settings/index.tsx:755 +msgid "Download Bluesky account data (repository)" +msgstr "Lataa Bluesky-tilin tiedot (repository)" + +#: src/view/screens/Settings/ExportCarDialog.tsx:59 +#: src/view/screens/Settings/ExportCarDialog.tsx:63 +msgid "Download CAR file" +msgstr "Lataa CAR tiedosto" + +#: src/view/com/composer/text-input/TextInput.web.tsx:249 +msgid "Drop to add images" +msgstr "Raahaa tähän lisätäksesi kuvia" + +#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:111 +msgid "Due to Apple policies, adult content can only be enabled on the web after completing sign up." +msgstr "Applen sääntöjen vuoksi aikuisviihde voidaan ottaa käyttöön vasta rekisteröitymisen jälkeen." + +#: src/view/com/modals/EditProfile.tsx:185 +msgid "e.g. Alice Roberts" +msgstr "esim. Mikko Mallikas" + +#: src/view/com/modals/EditProfile.tsx:203 +msgid "e.g. Artist, dog-lover, and avid reader." +msgstr "esim. Taiteilija, koiraharrastaja ja innokas lukija." + +#: src/view/com/modals/CreateOrEditList.tsx:283 +msgid "e.g. Great Posters" +msgstr "esim. Loistavat kirjoittajat" + +#: src/view/com/modals/CreateOrEditList.tsx:284 +msgid "e.g. Spammers" +msgstr "esim. Roskapostittajat" + +#: src/view/com/modals/CreateOrEditList.tsx:312 +msgid "e.g. The posters who never miss." +msgstr "esim. Julkaisijat, jotka osuvat maaliin aina." + +#: src/view/com/modals/CreateOrEditList.tsx:313 +msgid "e.g. Users that repeatedly reply with ads." +msgstr "esim. Käyttäjät, jotka vastaavat toistuvasti mainoksilla." + +#: src/view/com/modals/InviteCodes.tsx:96 +msgid "Each code works once. You'll receive more invite codes periodically." +msgstr "Jokainen koodi toimii vain kerran. Saat lisää kutsukoodeja säännöllisin väliajoin." + +#: src/view/com/lists/ListMembers.tsx:149 +msgctxt "action" +msgid "Edit" +msgstr "Muokkaa" + +#: src/view/com/composer/photos/Gallery.tsx:144 +#: src/view/com/modals/EditImage.tsx:207 +msgid "Edit image" +msgstr "Muokkaa kuvaa" + +#: src/view/screens/ProfileList.tsx:433 +msgid "Edit list details" +msgstr "Muokkaa listan tietoja" + +#: src/view/com/modals/CreateOrEditList.tsx:250 +msgid "Edit Moderation List" +msgstr "Muokkaa moderaatiolistaa" + +#: src/Navigation.tsx:242 +#: src/view/screens/Feeds.tsx:434 +#: src/view/screens/SavedFeeds.tsx:84 +msgid "Edit My Feeds" +msgstr "Muokkaa syötteitäni" + +#: src/view/com/modals/EditProfile.tsx:152 +msgid "Edit my profile" +msgstr "Muokkaa profiiliani" + +#: src/view/com/profile/ProfileHeader.tsx:418 +msgid "Edit profile" +msgstr "Muokkaa profiilia" + +#: src/view/com/profile/ProfileHeader.tsx:423 +msgid "Edit Profile" +msgstr "Muokkaa profiilia" + +#: src/view/com/home/HomeHeaderLayout.web.tsx:59 +#: src/view/screens/Feeds.tsx:355 +msgid "Edit Saved Feeds" +msgstr "Muokkaa tallennettuja syötteitä" + +#: src/view/com/modals/CreateOrEditList.tsx:245 +msgid "Edit User List" +msgstr "Muokkaa käyttäjälistaa" + +#: src/view/com/modals/EditProfile.tsx:193 +msgid "Edit your display name" +msgstr "Muokkaa näyttönimeäsi" + +#: src/view/com/modals/EditProfile.tsx:211 +msgid "Edit your profile description" +msgstr "Muokkaa profiilin kuvausta" + +#: src/screens/Onboarding/index.tsx:34 +msgid "Education" +msgstr "Koulutus" + +#: src/view/com/auth/create/Step1.tsx:199 +#: src/view/com/auth/login/ForgotPasswordForm.tsx:156 +#: src/view/com/modals/ChangeEmail.tsx:141 +#: src/view/com/modals/Waitlist.tsx:88 +msgid "Email" +msgstr "Sähköposti" + +#: src/view/com/auth/create/Step1.tsx:190 +#: src/view/com/auth/login/ForgotPasswordForm.tsx:147 +msgid "Email address" +msgstr "Sähköpostiosoite" + +#: src/view/com/modals/ChangeEmail.tsx:56 +#: src/view/com/modals/ChangeEmail.tsx:88 +msgid "Email updated" +msgstr "Sähköpostiosoite päivitetty" + +#: src/view/com/modals/ChangeEmail.tsx:111 +msgid "Email Updated" +msgstr "Sähköpostiosoite päivitetty" + +#: src/view/com/modals/VerifyEmail.tsx:78 +msgid "Email verified" +msgstr "Sähköpostiosoite vahvistettu" + +#: src/view/screens/Settings/index.tsx:312 +msgid "Email:" +msgstr "Sähköpostiosoite:" + +#: src/view/com/modals/EmbedConsent.tsx:113 +msgid "Enable {0} only" +msgstr "Ota käyttöön vain {0}" + +#: src/view/com/modals/ContentFilteringSettings.tsx:167 +msgid "Enable Adult Content" +msgstr "Ota aikuissisältö käyttöön" + +#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:76 +#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:77 +msgid "Enable adult content in your feeds" +msgstr "Näytä aikuissisältöä syötteissäsi" + +#: src/view/com/modals/EmbedConsent.tsx:97 +msgid "Enable External Media" +msgstr "Ota ulkoinen media käyttöön" + +#: src/view/screens/PreferencesExternalEmbeds.tsx:75 +msgid "Enable media players for" +msgstr "Ota mediatoistimet käyttöön kohteille" + +#: src/view/screens/PreferencesFollowingFeed.tsx:147 +msgid "Enable this setting to only see replies between people you follow." +msgstr "Ota tämä asetus käyttöön nähdäksesi vastaukset vain seuraamiltasi ihmisiltä." + +#: src/view/screens/Profile.tsx:455 +msgid "End of feed" +msgstr "Syötteen loppu" + +#: src/view/com/modals/AddAppPasswords.tsx:166 +msgid "Enter a name for this App Password" +msgstr "Anna sovellusalasanalle nimi" + +#: src/components/dialogs/MutedWords.tsx:87 +#: src/components/dialogs/MutedWords.tsx:88 +msgid "Enter a word or tag" +msgstr "Kirjoita sana tai tunniste" + +#: src/view/com/modals/VerifyEmail.tsx:105 +msgid "Enter Confirmation Code" +msgstr "Syötä vahvistuskoodi" + +#: src/view/com/modals/ChangePassword.tsx:151 +msgid "Enter the code you received to change your password." +msgstr "Anna saamasi koodi vaihtaaksesi salasanasi." + +#: src/view/com/modals/ChangeHandle.tsx:371 +msgid "Enter the domain you want to use" +msgstr "Anna verkkotunnus, jota haluat käyttää" + +#: src/view/com/auth/login/ForgotPasswordForm.tsx:107 +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 "Anna sähköpostiosoite, jotaa käytit tilin luomiseen. Lähetämme sinulle \"nollauskoodin\", jotta voit määritellä uuden salasanan." + +#: src/view/com/auth/create/Step1.tsx:251 +#: src/view/com/modals/BirthDateSettings.tsx:74 +msgid "Enter your birth date" +msgstr "Syötä syntymäaikasi" + +#: src/view/com/modals/Waitlist.tsx:78 +msgid "Enter your email" +msgstr "Syötä sähköpostiosoitteesi" + +#: src/view/com/auth/create/Step1.tsx:195 +msgid "Enter your email address" +msgstr "Syötä sähköpostiosoitteesi" + +#: src/view/com/modals/ChangeEmail.tsx:41 +msgid "Enter your new email above" +msgstr "Syötä uusi sähköpostiosoitteesi yläpuolelle" + +#: src/view/com/modals/ChangeEmail.tsx:117 +msgid "Enter your new email address below." +msgstr "Syötä uusi sähköpostiosoitteesi alle" + +#: src/view/com/auth/create/Step2.tsx:188 +#~ msgid "Enter your phone number" +msgstr "Syötä puhelinnumerosi" + +#: src/view/com/auth/login/Login.tsx:99 +msgid "Enter your username and password" +msgstr "Syötä käyttäjätunnuksesi ja salasanasi" + +#: src/view/com/auth/create/Step3.tsx:67 +msgid "Error receiving captcha response." +msgstr "Virhe captcha-vastauksen vastaanottamisessa." + +#: src/view/screens/Search/Search.tsx:110 +msgid "Error:" +msgstr "Virhe:" + +#: src/view/com/modals/Threadgate.tsx:76 +msgid "Everybody" +msgstr "Kaikki" + +#: src/view/com/modals/ChangeHandle.tsx:150 +msgid "Exits handle change process" +msgstr "Peruuttaa käyttäjätunnuksen vaihtamisen" + +#: src/view/com/lightbox/Lightbox.web.tsx:120 +msgid "Exits image view" +msgstr "Poistuu kuvan katselutilasta" + +#: src/view/com/modals/ListAddRemoveUsers.tsx:88 +#: src/view/shell/desktop/Search.tsx:235 +msgid "Exits inputting search query" +msgstr "Poistuu hakukyselyn kirjoittamisesta" + +#: src/view/com/modals/Waitlist.tsx:138 +msgid "Exits signing up for waitlist with {email}" +msgstr "Poistuu odotuslistalle liittymisestä sähköpostilla {email}" + +#: src/view/com/lightbox/Lightbox.web.tsx:163 +msgid "Expand alt text" +msgstr "Laajenna ALT-teksti" + +#: src/view/com/composer/ComposerReplyTo.tsx:81 +#: src/view/com/composer/ComposerReplyTo.tsx:84 +msgid "Expand or collapse the full post you are replying to" +msgstr "Laajenna tai pienennä viesti johon olit vastaamassa" + +#: src/view/screens/Settings/index.tsx:753 +msgid "Export my data" +msgstr "Vie tietoni" + +#: src/view/screens/Settings/ExportCarDialog.tsx:44 +#: src/view/screens/Settings/index.tsx:764 +msgid "Export My Data" +msgstr "Vie tietoni" + +#: src/view/com/modals/EmbedConsent.tsx:64 +msgid "External Media" +msgstr "Ulkoiset mediat" + +#: src/view/com/modals/EmbedConsent.tsx:75 +#: src/view/screens/PreferencesExternalEmbeds.tsx:66 +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 "Ulkoiset mediat voivat sallia verkkosivustojen kerätä tietoja sinusta ja laitteestasi. Tietoja ei lähetetä eikä pyydetä, ennen kuin painat \"toista\"-painiketta." + +#: src/Navigation.tsx:261 +#: src/view/screens/PreferencesExternalEmbeds.tsx:52 +#: src/view/screens/Settings/index.tsx:657 +msgid "External Media Preferences" +msgstr "Ulkoisten medioiden asetukset" + +#: src/view/screens/Settings/index.tsx:648 +msgid "External media settings" +msgstr "Ulkoisten medioiden asetukset" + +#: src/view/com/modals/AddAppPasswords.tsx:115 +#: src/view/com/modals/AddAppPasswords.tsx:119 +msgid "Failed to create app password." +msgstr "Sovellussalasanan luominen epäonnistui." + +#: src/view/com/modals/CreateOrEditList.tsx:206 +msgid "Failed to create the list. Check your internet connection and try again." +msgstr "Listan luominen epäonnistui. Tarkista internetyhteytesi ja yritä uudelleen." + +#: src/view/com/util/forms/PostDropdownBtn.tsx:110 +msgid "Failed to delete post, please try again" +msgstr "Viestin poistaminen epäonnistui, yritä uudelleen" + +#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:109 +#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:141 +msgid "Failed to load recommended feeds" +msgstr "Suositeltujen syötteiden lataaminen epäonnistui" + +#: src/Navigation.tsx:192 +msgid "Feed" +msgstr "Syöte" + +#: src/view/com/feeds/FeedSourceCard.tsx:231 +msgid "Feed by {0}" +msgstr "Syöte käyttäjältä {0}" + +#: src/view/screens/Feeds.tsx:605 +msgid "Feed offline" +msgstr "Syöte ei ole käytettävissä" + +#: src/view/com/feeds/FeedPage.tsx:143 +#~ msgid "Feed Preferences" +#~ msgstr "Syötteen asetukset" + +#: src/view/shell/desktop/RightNav.tsx:61 +#: src/view/shell/Drawer.tsx:311 +msgid "Feedback" +msgstr "Palaute" + +#: src/Navigation.tsx:442 +#: src/view/screens/Feeds.tsx:419 +#: src/view/screens/Feeds.tsx:524 +#: src/view/screens/Profile.tsx:184 +#: src/view/shell/bottom-bar/BottomBar.tsx:181 +#: src/view/shell/desktop/LeftNav.tsx:342 +#: src/view/shell/Drawer.tsx:476 +#: src/view/shell/Drawer.tsx:477 +msgid "Feeds" +msgstr "Syötteet" + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 +#~ msgid "Feeds are created by users and can give you entirely new experiences." +#~ msgstr "" + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 +#~ msgid "Feeds are created by users and organizations. They offer you varied experiences and suggest content you may like using algorithms." +#~ msgstr "" + +#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:57 +msgid "Feeds are created by users to curate content. Choose some feeds that you find interesting." +msgstr "Käyttäjät luovat syötteitä sisällön kuratointiin. Valitse joitakin syötteitä, jotka koet mielenkiintoisiksi." + +#: src/view/screens/SavedFeeds.tsx:156 +msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." +msgstr "Syötteet ovat käyttäjien rakentamia mukautettuja algoritmeja, jotka vaativat vain vähän koodaustaitoja. <0/> lisätietoa varten." + +#: src/screens/Onboarding/StepTopicalFeeds.tsx:76 +msgid "Feeds can be topical as well!" +msgstr "Syötteet voivat olla myös aihepiirikohtaisia!" + +#: src/screens/Onboarding/StepFinished.tsx:151 +msgid "Finalizing" +msgstr "Viimeistely" + +#: src/view/com/posts/CustomFeedEmptyState.tsx:47 +#: src/view/com/posts/FollowingEmptyState.tsx:57 +#: src/view/com/posts/FollowingEndOfFeed.tsx:58 +msgid "Find accounts to follow" +msgstr "Etsi seurattavia tilejä" + +#: src/view/screens/Search/Search.tsx:440 +msgid "Find users on Bluesky" +msgstr "Etsi käyttäjiä Bluesky-palvelusta" + +#: src/view/screens/Search/Search.tsx:438 +msgid "Find users with the search tool on the right" +msgstr "Etsi käyttäjiä oikealla olevan hakutyökalun avulla" + +#: src/view/com/auth/onboarding/RecommendedFollowsItem.tsx:150 +msgid "Finding similar accounts..." +msgstr "Etsitään samankaltaisia käyttäjätilejä" + +#: src/view/screens/PreferencesFollowingFeed.tsx:111 +msgid "Fine-tune the content you see on your Following feed." +msgstr "Hienosäädä näkemääsi sisältöä Seuraavat-syötteessäsi." + +#: src/view/screens/PreferencesHomeFeed.tsx:111 +#~ msgid "Fine-tune the content you see on your home screen." +#~ msgstr "Hienosäädä näkemääsi sisältöä pääsivulla." + +#: src/view/screens/PreferencesThreads.tsx:60 +msgid "Fine-tune the discussion threads." +msgstr "Hienosäädä keskusteluketjuja." + +#: src/screens/Onboarding/index.tsx:38 +msgid "Fitness" +msgstr "Kuntoilu" + +#: src/screens/Onboarding/StepFinished.tsx:131 +msgid "Flexible" +msgstr "Joustava" + +#: src/view/com/modals/EditImage.tsx:115 +msgid "Flip horizontal" +msgstr "Käännä vaakasuunnassa" + +#: src/view/com/modals/EditImage.tsx:120 +#: src/view/com/modals/EditImage.tsx:287 +msgid "Flip vertically" +msgstr "Käännä pystysuunnassa" + +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:181 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:136 +#: src/view/com/profile/ProfileHeader.tsx:513 +msgid "Follow" +msgstr "Seuraa" + +#: src/view/com/profile/FollowButton.tsx:64 +msgctxt "action" +msgid "Follow" +msgstr "Seuraa" + +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:58 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:122 +#: src/view/com/profile/ProfileHeader.tsx:504 +msgid "Follow {0}" +msgstr "Seuraa {0}" + +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:179 +msgid "Follow All" +msgstr "Seuraa kaikkia" + +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:174 +msgid "Follow selected accounts and continue to the next step" +msgstr "Seuraa valittuja tilejä ja siirry seuraavaan vaiheeseen" + +#: src/view/com/auth/onboarding/RecommendedFollows.tsx:64 +msgid "Follow some users to get started. We can recommend you more users based on who you find interesting." +msgstr "Seuraa joitakin käyttäjiä aloittaaksesi. Suosittelemme sinulle lisää käyttäjiä sen perusteella, ketä pidät mielenkiintoisena." + +#: src/view/com/profile/ProfileCard.tsx:194 +msgid "Followed by {0}" +msgstr "Seuraajina {0}" + +#: src/view/com/modals/Threadgate.tsx:98 +msgid "Followed users" +msgstr "Seuratut käyttäjät" + +#: src/view/screens/PreferencesFollowingFeed.tsx:154 +msgid "Followed users only" +msgstr "Vain seuratut käyttäjät" + +#: src/view/com/notifications/FeedItem.tsx:166 +msgid "followed you" +msgstr "seurasi sinua" + +#: src/view/screens/ProfileFollowers.tsx:25 +msgid "Followers" +msgstr "Seuraajat" + +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:136 +#: src/view/com/profile/ProfileHeader.tsx:495 +#: src/view/screens/ProfileFollows.tsx:25 +msgid "Following" +msgstr "Seuraa" + +#: src/view/com/profile/ProfileHeader.tsx:149 +msgid "Following {0}" +msgstr "Seuraa {0}" + +#: src/Navigation.tsx:248 +#: src/view/com/home/HomeHeaderLayout.web.tsx:45 +#: src/view/com/home/HomeHeaderLayoutMobile.tsx:83 +#: src/view/screens/PreferencesFollowingFeed.tsx:104 +#: src/view/screens/Settings/index.tsx:543 +msgid "Following Feed Preferences" +msgstr "Seuratut -syötteen asetukset" + +#: src/view/com/profile/ProfileHeader.tsx:546 +msgid "Follows you" +msgstr "Seuraa sinua" + +#: src/view/com/profile/ProfileCard.tsx:141 +msgid "Follows You" +msgstr "Seuraa sinua" + +#: src/screens/Onboarding/index.tsx:43 +msgid "Food" +msgstr "Ruoka" + +#: src/view/com/modals/DeleteAccount.tsx:111 +msgid "For security reasons, we'll need to send a confirmation code to your email address." +msgstr "Turvallisuussyistä meidän on lähetettävä vahvistuskoodi sähköpostiosoitteeseesi." + +#: src/view/com/modals/AddAppPasswords.tsx:209 +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 "Turvallisuussyistä et näe tätä uudelleen. Jos unohdat tämän salasanan, sinun on luotava uusi." + +#: src/view/com/auth/login/LoginForm.tsx:241 +msgid "Forgot" +msgstr "Unohtui" + +#: src/view/com/auth/login/LoginForm.tsx:238 +msgid "Forgot password" +msgstr "Unohtunut salasana" + +#: src/view/com/auth/login/Login.tsx:127 +#: src/view/com/auth/login/Login.tsx:143 +msgid "Forgot Password" +msgstr "Unohtunut salasana" + +#: src/view/com/posts/FeedItem.tsx:189 +msgctxt "from-feed" +msgid "From <0/>" +msgstr "Lähde: <0/>" + +#: src/view/com/composer/photos/SelectPhotoBtn.tsx:43 +msgid "Gallery" +msgstr "Galleria" + +#: src/view/com/modals/VerifyEmail.tsx:189 +#: src/view/com/modals/VerifyEmail.tsx:191 +msgid "Get Started" +msgstr "Aloita tästä" + +#: src/view/com/auth/LoggedOut.tsx:81 +#: src/view/com/auth/LoggedOut.tsx:82 +#: src/view/com/util/moderation/ScreenHider.tsx:123 +#: src/view/shell/desktop/LeftNav.tsx:104 +msgid "Go back" +msgstr "Palaa takaisin" + +#: src/view/screens/ProfileFeed.tsx:106 +#: src/view/screens/ProfileFeed.tsx:111 +#: src/view/screens/ProfileList.tsx:897 +#: src/view/screens/ProfileList.tsx:902 +msgid "Go Back" +msgstr "Palaa takaisin" + +#: src/screens/Onboarding/Layout.tsx:104 +#: src/screens/Onboarding/Layout.tsx:193 +msgid "Go back to previous step" +msgstr "Palaa edelliseen vaiheeseen" + +#: src/view/screens/Search/Search.tsx:747 +#: src/view/shell/desktop/Search.tsx:262 +msgid "Go to @{queryMaybeHandle}" +msgstr "Siirry @{queryMaybeHandle}" + +#: src/view/com/auth/login/ForgotPasswordForm.tsx:189 +#: src/view/com/auth/login/ForgotPasswordForm.tsx:218 +#: src/view/com/auth/login/LoginForm.tsx:288 +#: src/view/com/auth/login/SetNewPasswordForm.tsx:195 +#: src/view/com/modals/ChangePassword.tsx:165 +msgid "Go to next" +msgstr "Siirry seuraavaan" + +#: src/view/com/modals/ChangeHandle.tsx:265 +msgid "Handle" +msgstr "Käyttäjätunnus" + +#: src/components/RichText.tsx:188 +msgid "Hashtag: {tag}" +msgstr "Tunniste: {tag}" + +#: src/view/com/auth/create/CreateAccount.tsx:208 +msgid "Having trouble?" +msgstr "Ongelmia?" + +#: src/view/shell/desktop/RightNav.tsx:90 +#: src/view/shell/Drawer.tsx:321 +msgid "Help" +msgstr "Ohje" + +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:132 +msgid "Here are some accounts for you to follow" +msgstr "Tässä on joitakin tilejä seurattavaksi" + +#: src/screens/Onboarding/StepTopicalFeeds.tsx:85 +msgid "Here are some popular topical feeds. You can choose to follow as many as you like." +msgstr "Tässä on joitakin suosittuja aihepiirikohtaisia syötteitä. Voit valita seurattavaksi niin monta kuin haluat." + +#: src/screens/Onboarding/StepTopicalFeeds.tsx:80 +msgid "Here are some topical feeds based on your interests: {interestsText}. You can choose to follow as many as you like." +msgstr "Tässä on joitakin aihepiirikohtaisia syötteitä kiinnostuksiesi perusteella: {interestsText}. Voit valita seurata niin montaa kuin haluat." + +#: src/view/com/modals/AddAppPasswords.tsx:153 +msgid "Here is your app password." +msgstr "Tässä on sovelluksesi salasana." + +#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:41 +#: src/view/com/modals/ContentFilteringSettings.tsx:251 +#: src/view/com/util/moderation/ContentHider.tsx:105 +#: src/view/com/util/moderation/PostHider.tsx:108 +msgid "Hide" +msgstr "Piilota" + +#: src/view/com/modals/ContentFilteringSettings.tsx:224 +#: src/view/com/notifications/FeedItem.tsx:325 +msgctxt "action" +msgid "Hide" +msgstr "Piilota" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:232 +msgid "Hide post" +msgstr "Piilota viesti" + +#: src/view/com/util/moderation/ContentHider.tsx:67 +#: src/view/com/util/moderation/PostHider.tsx:61 +msgid "Hide the content" +msgstr "Piilota sisältö" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:236 +msgid "Hide this post?" +msgstr "Piilota tämä viesti?" + +#: src/view/com/notifications/FeedItem.tsx:315 +msgid "Hide user list" +msgstr "Piilota käyttäjäluettelo" + +#: src/view/com/profile/ProfileHeader.tsx:487 +msgid "Hides posts from {0} in your feed" +msgstr "Piilottaa viestit käyttäjältä {0} syötteessäsi" + +#: src/view/com/posts/FeedErrorMessage.tsx:111 +msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." +msgstr "Hmm, jokin ongelma ilmeni ottaessa yhteyttä syötteen palvelimeen. Ilmoita asiasta syötteen omistajalle." + +#: src/view/com/posts/FeedErrorMessage.tsx:99 +msgid "Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue." +msgstr "Hmm, syötteen palvelin vaikuttaa olevan väärin konfiguroitu. Ilmoita asiasta syötteen omistajalle." + +#: src/view/com/posts/FeedErrorMessage.tsx:105 +msgid "Hmm, the feed server appears to be offline. Please let the feed owner know about this issue." +msgstr "Hmm, syötteen palvelin vaikuttaa olevan poissa käytöstä. Ilmoita asiasta syötteen omistajalle." + +#: src/view/com/posts/FeedErrorMessage.tsx:102 +msgid "Hmm, the feed server gave a bad response. Please let the feed owner know about this issue." +msgstr "Hmm, syötteen palvelin antoi virheellisen vastauksen. Ilmoita asiasta syötteen omistajalle." + +#: src/view/com/posts/FeedErrorMessage.tsx:96 +msgid "Hmm, we're having trouble finding this feed. It may have been deleted." +msgstr "Hmm, meillä on vaikeuksia löytää tätä syötettä. Se saattaa olla poistettu." + +#: src/Navigation.tsx:435 +#: src/view/shell/bottom-bar/BottomBar.tsx:137 +#: src/view/shell/desktop/LeftNav.tsx:306 +#: src/view/shell/Drawer.tsx:398 +#: src/view/shell/Drawer.tsx:399 +msgid "Home" +msgstr "Koti" + +#: src/Navigation.tsx:247 +#: src/view/com/pager/FeedsTabBarMobile.tsx:123 +#: src/view/screens/PreferencesHomeFeed.tsx:104 +#: src/view/screens/Settings/index.tsx:543 +#~ msgid "Home Feed Preferences" +#~ msgstr "Aloitussivun syötteiden asetukset" + +#: src/view/com/auth/create/Step1.tsx:82 +#: src/view/com/auth/login/ForgotPasswordForm.tsx:120 +msgid "Hosting provider" +msgstr "Hostingyritys" + +#: src/view/com/modals/InAppBrowserConsent.tsx:44 +msgid "How should we open this link?" +msgstr "Kuinka haluat avata tämän linkin?" + +#: src/view/com/modals/VerifyEmail.tsx:214 +msgid "I have a code" +msgstr "Minulla on koodi" + +#: src/view/com/modals/VerifyEmail.tsx:216 +msgid "I have a confirmation code" +msgstr "Minulla on vahvistuskoodi" + +#: src/view/com/modals/ChangeHandle.tsx:283 +msgid "I have my own domain" +msgstr "Minulla on oma verkkotunnus" + +#: src/view/com/lightbox/Lightbox.web.tsx:165 +msgid "If alt text is long, toggles alt text expanded state" +msgstr "Jos ALT-teksti on pitkä, vaihtaa ALT-tekstin laajennetun tilan" + +#: src/view/com/modals/SelfLabel.tsx:127 +msgid "If none are selected, suitable for all ages." +msgstr "Jos mitään ei ole valittu, sopii kaikenikäisille." + +#: src/view/com/modals/ChangePassword.tsx:146 +msgid "If you want to change your password, we will send you a code to verify that this is your account." +msgstr "Jos haluat vaihtaa salasanasi, lähetämme sinulle koodin varmistaaksemme, että tämä on tilisi." + +#: src/view/com/util/images/Gallery.tsx:38 +msgid "Image" +msgstr "Kuva" + +#: src/view/com/modals/AltImage.tsx:120 +msgid "Image alt text" +msgstr "Kuvan ALT-teksti" + +#: src/view/com/util/UserAvatar.tsx:311 +#: src/view/com/util/UserBanner.tsx:118 +msgid "Image options" +msgstr "Kuva-asetukset" + +#: src/view/com/auth/login/SetNewPasswordForm.tsx:138 +msgid "Input code sent to your email for password reset" +msgstr "Syötä sähköpostiisi lähetetty koodi salasanan nollaamista varten" + +#: src/view/com/modals/DeleteAccount.tsx:184 +msgid "Input confirmation code for account deletion" +msgstr "Syötä vahvistuskoodi tilin poistoa varten" + +#: src/view/com/auth/create/Step1.tsx:200 +msgid "Input email for Bluesky account" +msgstr "Syötä sähköposti Bluesky-tiliä varten" + +#: src/view/com/auth/create/Step1.tsx:158 +msgid "Input invite code to proceed" +msgstr "Syötä kutsukoodi jatkaaksesi" + +#: src/view/com/modals/AddAppPasswords.tsx:180 +msgid "Input name for app password" +msgstr "Syötä nimi sovellussalasanaa varten" + +#: src/view/com/auth/login/SetNewPasswordForm.tsx:162 +msgid "Input new password" +msgstr "Syötä uusi salasana" + +#: src/view/com/modals/DeleteAccount.tsx:203 +msgid "Input password for account deletion" +msgstr "Syötä salasana tilin poistoa varten" + +#: src/view/com/auth/create/Step2.tsx:196 +#~ msgid "Input phone number for SMS verification" +#~ msgstr "Syötä puhelinnumero SMS-varmennusta varten" + +#: src/view/com/auth/login/LoginForm.tsx:230 +msgid "Input the password tied to {identifier}" +msgstr "Syötä salasana, joka liittyy kohteeseen {identifier}" + +#: src/view/com/auth/login/LoginForm.tsx:197 +msgid "Input the username or email address you used at signup" +msgstr "Syötä käyttäjätunnus tai sähköpostiosoite, jonka käytit rekisteröityessäsi" + +#: src/view/com/auth/create/Step2.tsx:271 +#~ msgid "Input the verification code we have texted to you" +#~ msgstr "Syötä sinulle tekstattu varmennuskoodi" + +#: src/view/com/modals/Waitlist.tsx:90 +msgid "Input your email to get on the Bluesky waitlist" +msgstr "Syötä sähköpostiosoitteesi päästäksesi Bluesky-jonoon" + +#: src/view/com/auth/login/LoginForm.tsx:229 +msgid "Input your password" +msgstr "Syötä salasanasi" + +#: src/view/com/auth/create/Step2.tsx:80 +msgid "Input your user handle" +msgstr "Syötä käyttäjätunnuksesi" + +#: src/view/com/post-thread/PostThreadItem.tsx:226 +msgid "Invalid or unsupported post record" +msgstr "Virheellinen tai ei tuettu tietue" + +#: src/view/com/auth/login/LoginForm.tsx:113 +msgid "Invalid username or password" +msgstr "Virheellinen käyttäjätunnus tai salasana" + +#: src/view/screens/Settings.tsx:411 +#~ msgid "Invite" +#~ msgstr "" + +#: src/view/com/modals/InviteCodes.tsx:93 +msgid "Invite a Friend" +msgstr "Kutsu ystävä" + +#: src/view/com/auth/create/Step1.tsx:148 +#: src/view/com/auth/create/Step1.tsx:157 +msgid "Invite code" +msgstr "Kutsukoodi" + +#: src/view/com/auth/create/state.ts:158 +msgid "Invite code not accepted. Check that you input it correctly and try again." +msgstr "Kutsukoodia ei hyväksytty. Tarkista, että syötit sen oikein ja yritä uudelleen." + +#: src/view/com/modals/InviteCodes.tsx:170 +msgid "Invite codes: {0} available" +msgstr "Kutsukoodit: {0} saatavilla" + +#: src/view/shell/Drawer.tsx:645 +#~ msgid "Invite codes: {invitesAvailable} available" +#~ msgstr "" + +#: src/view/com/modals/InviteCodes.tsx:169 +msgid "Invite codes: 1 available" +msgstr "Kutsukoodit: 1 saatavilla" + +#: src/screens/Onboarding/StepFollowingFeed.tsx:64 +msgid "It shows posts from the people you follow as they happen." +msgstr "Se näyttää viestejä seuraamiltasi ihmisiltä reaaliajassa." + +#: src/view/com/auth/HomeLoggedOutCTA.tsx:99 +#: src/view/com/auth/SplashScreen.web.tsx:138 +msgid "Jobs" +msgstr "Työpaikat" + +#: src/view/com/modals/Waitlist.tsx:67 +msgid "Join the waitlist" +msgstr "Liity odotuslistalle" + +#: src/view/com/auth/create/Step1.tsx:174 +#: src/view/com/auth/create/Step1.tsx:178 +msgid "Join the waitlist." +msgstr "Liity odotuslistalle." + +#: src/view/com/modals/Waitlist.tsx:128 +msgid "Join Waitlist" +msgstr "Liity odotuslistalle" + +#: src/screens/Onboarding/index.tsx:24 +msgid "Journalism" +msgstr "Journalismi" + +#: src/view/com/composer/select-language/SelectLangBtn.tsx:104 +msgid "Language selection" +msgstr "Kielen valinta" + +#: src/view/screens/Settings/index.tsx:594 +msgid "Language settings" +msgstr "Kielen asetukset" + +#: src/Navigation.tsx:140 +#: src/view/screens/LanguageSettings.tsx:89 +msgid "Language Settings" +msgstr "Kielen asetukset" + +#: src/view/screens/Settings/index.tsx:603 +msgid "Languages" +msgstr "Kielet" + +#: src/view/com/auth/create/StepHeader.tsx:20 +msgid "Last step!" +msgstr "Viimeinen vaihe!" + +#: src/view/com/util/moderation/ContentHider.tsx:103 +msgid "Learn more" +msgstr "Lue lisää" + +#: src/view/com/util/moderation/PostAlerts.tsx:47 +#: src/view/com/util/moderation/ProfileHeaderAlerts.tsx:65 +#: src/view/com/util/moderation/ScreenHider.tsx:104 +msgid "Learn More" +msgstr "Lue lisää" + +#: src/view/com/util/moderation/ContentHider.tsx:85 +#: src/view/com/util/moderation/PostAlerts.tsx:40 +#: src/view/com/util/moderation/PostHider.tsx:78 +#: src/view/com/util/moderation/ProfileHeaderAlerts.tsx:49 +#: src/view/com/util/moderation/ScreenHider.tsx:101 +msgid "Learn more about this warning" +msgstr "Lue lisää tästä varoituksesta" + +#: src/view/screens/Moderation.tsx:262 +msgid "Learn more about what is public on Bluesky." +msgstr "Lue lisää siitä, mikä on julkista Blueskyssa." + +#: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:82 +msgid "Leave them all unchecked to see any language." +msgstr "Jätä kaikki valitsematta nähdäksesi minkä tahansa kielen." + +#: src/view/com/modals/LinkWarning.tsx:51 +msgid "Leaving Bluesky" +msgstr "Poistuminen Blueskysta" + +#: src/screens/Deactivated.tsx:128 +msgid "left to go." +msgstr "jäljellä." + +#: src/view/screens/Settings/index.tsx:278 +msgid "Legacy storage cleared, you need to restart the app now." +msgstr "Legacy tietovarasto tyhjennetty, sinun on käynnistettävä sovellus uudelleen nyt." + +#: src/view/com/auth/login/Login.tsx:128 +#: src/view/com/auth/login/Login.tsx:144 +msgid "Let's get your password reset!" +msgstr "Aloitetaan salasanasi nollaus!" + +#: src/screens/Onboarding/StepFinished.tsx:151 +msgid "Let's go!" +msgstr "Aloitetaan!" + +#: src/view/com/util/UserAvatar.tsx:248 +#: src/view/com/util/UserBanner.tsx:62 +msgid "Library" +msgstr "Kirjasto" + +#: src/view/screens/Settings/index.tsx:479 +msgid "Light" +msgstr "Vaalea" + +#: src/view/com/util/post-ctrls/PostCtrls.tsx:182 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:216 +msgid "Like" +msgstr "Tykkää" + +#: src/view/screens/ProfileFeed.tsx:591 +msgid "Like this feed" +msgstr "Tykkää tästä syötteestä" + +#: src/Navigation.tsx:197 +msgid "Liked by" +msgstr "Tykänneet" + +#: src/view/screens/PostLikedBy.tsx:27 +#: src/view/screens/ProfileFeedLikedBy.tsx:27 +msgid "Liked By" +msgstr "Tykänneet" + +#: src/view/com/feeds/FeedSourceCard.tsx:279 +msgid "Liked by {0} {1}" +msgstr "Tykänneet {0} {1}" + +#: src/view/screens/ProfileFeed.tsx:606 +msgid "Liked by {likeCount} {0}" +msgstr "Tykkäyksiä {likeCount} {0}" + +#: src/view/com/notifications/FeedItem.tsx:170 +msgid "liked your custom feed" +msgstr "tykkäsi mukautetusta syötteestäsi" + +#: src/view/com/notifications/FeedItem.tsx:155 +msgid "liked your post" +msgstr "tykkäsi viestistäsi" + +#: src/view/screens/Profile.tsx:183 +msgid "Likes" +msgstr "Tykkäykset" + +#: src/view/com/post-thread/PostThreadItem.tsx:183 +msgid "Likes on this post" +msgstr "Tykkäykset tässä viestissä" + +#: src/Navigation.tsx:166 +msgid "List" +msgstr "Lista" + +#: src/view/com/modals/CreateOrEditList.tsx:261 +msgid "List Avatar" +msgstr "Listan kuvake" + +#: src/view/screens/ProfileList.tsx:324 +msgid "List blocked" +msgstr "Lista estetty" + +#: src/view/com/feeds/FeedSourceCard.tsx:233 +msgid "List by {0}" +msgstr "Listan on luonut {0}" + +#: src/view/screens/ProfileList.tsx:378 +msgid "List deleted" +msgstr "Lista poistettu" + +#: src/view/screens/ProfileList.tsx:283 +msgid "List muted" +msgstr "Lista hiljennetty" + +#: src/view/com/modals/CreateOrEditList.tsx:275 +msgid "List Name" +msgstr "Listan nimi" + +#: src/view/screens/ProfileList.tsx:343 +msgid "List unblocked" +msgstr "Listaa estosta poistetut" + +#: src/view/screens/ProfileList.tsx:302 +msgid "List unmuted" +msgstr "Listaa hiljennyksestä poistetut" + +#: src/Navigation.tsx:110 +#: src/view/screens/Profile.tsx:185 +#: src/view/shell/desktop/LeftNav.tsx:379 +#: src/view/shell/Drawer.tsx:492 +#: src/view/shell/Drawer.tsx:493 +msgid "Lists" +msgstr "Listat" + +#: src/view/com/post-thread/PostThread.tsx:333 +#: src/view/com/post-thread/PostThread.tsx:341 +msgid "Load more posts" +msgstr "Lataa lisää viestejä" + +#: src/view/screens/Notifications.tsx:159 +msgid "Load new notifications" +msgstr "Lataa uusia ilmoituksia" + +#: src/view/com/feeds/FeedPage.tsx:115 +#: src/view/screens/Profile.tsx:440 +#: src/view/screens/ProfileFeed.tsx:495 +#: src/view/screens/ProfileList.tsx:681 +msgid "Load new posts" +msgstr "Lataa uusia viestejä" + +#: src/view/com/composer/text-input/mobile/Autocomplete.tsx:95 +msgid "Loading..." +msgstr "Ladataan..." + +#: src/view/com/modals/ServerInput.tsx:50 +#~ msgid "Local dev server" +#~ msgstr "" + +#: src/Navigation.tsx:207 +msgid "Log" +msgstr "Loki" + +#: src/screens/Deactivated.tsx:149 +#: src/screens/Deactivated.tsx:152 +#: src/screens/Deactivated.tsx:178 +#: src/screens/Deactivated.tsx:181 +msgid "Log out" +msgstr "Kirjaudu ulos" + +#: src/view/screens/Moderation.tsx:155 +msgid "Logged-out visibility" +msgstr "Näkyvyys kirjautumattomana" + +#: src/view/com/auth/login/ChooseAccountForm.tsx:133 +msgid "Login to account that is not listed" +msgstr "Kirjaudu tiliin, joka ei ole luettelossa" + +#: src/view/com/modals/LinkWarning.tsx:65 +msgid "Make sure this is where you intend to go!" +msgstr "Varmista, että olet menossa oikeaan paikkaan!" + +#: src/components/dialogs/MutedWords.tsx:71 +msgid "Manage your muted words and tags" +msgstr "Hallinnoi hiljennettyjä sanojasi ja tunnisteitasi" + +#: src/view/com/auth/create/Step2.tsx:118 +msgid "May not be longer than 253 characters" +msgstr "Ei saa olla pidempi kuin 253 merkkiä" + +#: src/view/com/auth/create/Step2.tsx:109 +msgid "May only contain letters and numbers" +msgstr "Ei saa olla pidempi kuin 253 merkkiä" + +#: src/view/screens/Profile.tsx:182 +msgid "Media" +msgstr "Media" + +#: src/view/com/threadgate/WhoCanReply.tsx:139 +msgid "mentioned users" +msgstr "mainitut käyttäjät" + +#: src/view/com/modals/Threadgate.tsx:93 +msgid "Mentioned users" +msgstr "Mainitut käyttäjät" + +#: src/view/com/util/ViewHeader.tsx:81 +#: src/view/screens/Search/Search.tsx:623 +msgid "Menu" +msgstr "Valikko" + +#: src/view/com/posts/FeedErrorMessage.tsx:197 +msgid "Message from server: {0}" +msgstr "Viesti palvelimelta: {0}" + +#: src/Navigation.tsx:115 +#: src/view/screens/Moderation.tsx:66 +#: src/view/screens/Settings/index.tsx:625 +#: src/view/shell/desktop/LeftNav.tsx:397 +#: src/view/shell/Drawer.tsx:511 +#: src/view/shell/Drawer.tsx:512 +msgid "Moderation" +msgstr "Moderointi" + +#: src/view/com/lists/ListCard.tsx:93 +#: src/view/com/modals/UserAddRemoveLists.tsx:206 +msgid "Moderation list by {0}" +msgstr "Moderointilista käyttäjältä {0}" + +#: src/view/screens/ProfileList.tsx:774 +msgid "Moderation list by <0/>" +msgstr "Moderointilista käyttäjältä <0/>" + +#: src/view/com/lists/ListCard.tsx:91 +#: src/view/com/modals/UserAddRemoveLists.tsx:204 +#: src/view/screens/ProfileList.tsx:772 +msgid "Moderation list by you" +msgstr "Sinun moderointilistasi" + +#: src/view/com/modals/CreateOrEditList.tsx:197 +msgid "Moderation list created" +msgstr "Moderointilista luotu" + +#: src/view/com/modals/CreateOrEditList.tsx:183 +msgid "Moderation list updated" +msgstr "Moderointilista päivitetty" + +#: src/view/screens/Moderation.tsx:114 +msgid "Moderation lists" +msgstr "Moderointilistat" + +#: src/Navigation.tsx:120 +#: src/view/screens/ModerationModlists.tsx:58 +msgid "Moderation Lists" +msgstr "Moderointilistat" + +#: src/view/screens/Settings/index.tsx:619 +msgid "Moderation settings" +msgstr "Moderointiasetukset" + +#: src/view/com/modals/ModerationDetails.tsx:35 +msgid "Moderator has chosen to set a general warning on the content." +msgstr "Ylläpitäjä on asettanut yleisen varoituksen sisällölle." + +#: src/view/shell/desktop/Feeds.tsx:63 +msgid "More feeds" +msgstr "Lisää syötteitä" + +#: src/view/com/profile/ProfileHeader.tsx:523 +#: src/view/screens/ProfileFeed.tsx:363 +#: src/view/screens/ProfileList.tsx:617 +msgid "More options" +msgstr "Lisää asetuksia" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:315 +msgid "More post options" +msgstr "Lisää viestiasetuksia" + +#: src/view/screens/PreferencesThreads.tsx:82 +msgid "Most-liked replies first" +msgstr "Eniten tykätyt vastaukset ensin" + +#: src/view/com/auth/create/Step2.tsx:122 +msgid "Must be at least 3 characters" +msgstr "Täytyy olla vähintään 3 merkkiä" + +#: src/components/TagMenu/index.tsx:253 +msgid "Mute" +msgstr "Hiljennä" + +#: src/components/TagMenu/index.web.tsx:91 +msgid "Mute {truncatedTag}" +msgstr "Hiljennä {truncatedTag}" + +#: src/view/com/profile/ProfileHeader.tsx:327 +msgid "Mute Account" +msgstr "Hiljennä käyttäjä" + +#: src/view/screens/ProfileList.tsx:544 +msgid "Mute accounts" +msgstr "Hiljennä käyttäjät" +#: src/components/TagMenu/index.tsx:211 +msgid "Mute all {tag} posts" +msgstr "Hiljennä kaikki {tag}-viestit" + +#: src/components/dialogs/MutedWords.tsx:131 +msgid "Mute in tags only" +msgstr "Hiljennä vain tunnisteissa" + +#: src/components/dialogs/MutedWords.tsx:116 +msgid "Mute in text & tags" +msgstr "Hiljennä tekstissä ja tunnisteissa" + +#: src/view/screens/ProfileList.tsx:491 +msgid "Mute list" +msgstr "Hiljennä lista" + +#: src/view/screens/ProfileList.tsx:275 +msgid "Mute these accounts?" +msgstr "Hiljennä nämä käyttäjät?" + +#: src/view/screens/ProfileList.tsx:279 +msgid "Mute this List" +msgstr "Hiljennä tämä lista" + +#: src/components/dialogs/MutedWords.tsx:109 +msgid "Mute this word in post text and tags" +msgstr "Hiljennä tämä sana viesteissä ja tunnisteissa" + +#: src/components/dialogs/MutedWords.tsx:124 +msgid "Mute this word in tags only" +msgstr "Hiljennä tämä sana vain tunnisteissa" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:202 +msgid "Mute thread" +msgstr "Hiljennä keskustelu" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:216 +msgid "Mute words & tags" +msgstr "Hiljennä sanat ja tunnisteet" + +#: src/view/com/lists/ListCard.tsx:102 +msgid "Muted" +msgstr "Hiljennetty" + +#: src/view/screens/Moderation.tsx:128 +msgid "Muted accounts" +msgstr "Hiljennetyt käyttäjät" + +#: src/Navigation.tsx:125 +#: src/view/screens/ModerationMutedAccounts.tsx:107 +msgid "Muted Accounts" +msgstr "Hiljennetyt käyttäjätilit" + +#: src/view/screens/ModerationMutedAccounts.tsx:115 +msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private." +msgstr "Hiljennettyjen käyttäjien viestit poistetaan syötteestäsi ja ilmoituksistasi. Hiljennykset ovat täysin yksityisiä." + +#: src/view/screens/Moderation.tsx:100 +msgid "Muted words & tags" +msgstr "Hiljennetyt sanat ja tunnisteet" + +#: src/view/screens/ProfileList.tsx:277 +msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." +msgstr "Hiljennys on yksityinen. Hiljennetyt käyttäjät voivat edelleen vuorovaikuttaa kanssasi, mutta et näe heidän viestejään tai saa ilmoituksia heiltä." + +#: src/view/com/modals/BirthDateSettings.tsx:56 +msgid "My Birthday" +msgstr "Syntymäpäiväni" + +#: src/view/screens/Feeds.tsx:663 +msgid "My Feeds" +msgstr "Omat syötteet" + +#: src/view/shell/desktop/LeftNav.tsx:65 +msgid "My Profile" +msgstr "Profiilini" + +#: src/view/screens/Settings/index.tsx:582 +msgid "My Saved Feeds" +msgstr "Omat tallennetut syötteet" + +#: src/view/com/auth/server-input/index.tsx:118 +msgid "my-server.com" +msgstr "oma-palvelimeni.com" + +#: src/view/com/modals/AddAppPasswords.tsx:179 +#: src/view/com/modals/CreateOrEditList.tsx:290 +msgid "Name" +msgstr "Nimi" + +#: src/view/com/modals/CreateOrEditList.tsx:145 +msgid "Name is required" +msgstr "Nimi vaaditaan" + +#: src/screens/Onboarding/index.tsx:25 +msgid "Nature" +msgstr "Luonto" + +#: src/view/com/auth/login/ForgotPasswordForm.tsx:190 +#: src/view/com/auth/login/ForgotPasswordForm.tsx:219 +#: src/view/com/auth/login/LoginForm.tsx:289 +#: src/view/com/auth/login/SetNewPasswordForm.tsx:196 +#: src/view/com/modals/ChangePassword.tsx:166 +msgid "Navigates to the next screen" +msgstr "Siirtyy seuraavalle näytölle" + +#: src/view/shell/Drawer.tsx:71 +msgid "Navigates to your profile" +msgstr "Siirtyy profiiliisi" + +#: src/view/com/modals/EmbedConsent.tsx:107 +#: src/view/com/modals/EmbedConsent.tsx:123 +msgid "Never load embeds from {0}" +msgstr "Älä koskaan lataa upotuksia taholta {0}" + +#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:72 +#: src/view/com/auth/onboarding/WelcomeMobile.tsx:72 +msgid "Never lose access to your followers and data." +msgstr "Älä koskaan menetä pääsyä seuraajiisi ja tietoihisi." + +#: src/screens/Onboarding/StepFinished.tsx:119 +msgid "Never lose access to your followers or data." +msgstr "Älä koskaan menetä pääsyä seuraajiisi tai tietoihisi." + +#: src/components/dialogs/MutedWords.tsx:244 +msgid "Nevermind" +msgstr "Ei väliä" + +#: src/view/screens/Lists.tsx:76 +msgctxt "action" +msgid "New" +msgstr "Uusi" + +#: src/view/screens/ModerationModlists.tsx:78 +msgid "New" +msgstr "Uusi" + +#: src/view/com/modals/CreateOrEditList.tsx:252 +msgid "New Moderation List" +msgstr "Uusi moderointilista" + +#: src/view/com/auth/login/SetNewPasswordForm.tsx:150 +msgid "New password" +msgstr "Uusi salasana" + +#: src/view/com/modals/ChangePassword.tsx:215 +msgid "New Password" +msgstr "Uusi salasana" + +#: src/view/com/feeds/FeedPage.tsx:126 +msgctxt "action" +msgid "New post" +msgstr "Uusi viesti" + +#: src/view/screens/Feeds.tsx:555 +#: src/view/screens/Notifications.tsx:168 +#: src/view/screens/Profile.tsx:382 +#: src/view/screens/ProfileFeed.tsx:433 +#: src/view/screens/ProfileList.tsx:196 +#: src/view/screens/ProfileList.tsx:224 +#: src/view/shell/desktop/LeftNav.tsx:248 +msgid "New post" +msgstr "Uusi viesti" + +#: src/view/shell/desktop/LeftNav.tsx:258 +msgctxt "action" +msgid "New Post" +msgstr "Uusi viesti" + +#: src/view/com/modals/CreateOrEditList.tsx:247 +msgid "New User List" +msgstr "Uusi käyttäjälista" + +#: src/view/screens/PreferencesThreads.tsx:79 +msgid "Newest replies first" +msgstr "Uusimmat vastaukset ensin" + +#: src/screens/Onboarding/index.tsx:23 +msgid "News" +msgstr "Uutiset" + +#: src/view/com/auth/create/CreateAccount.tsx:172 +#: src/view/com/auth/login/ForgotPasswordForm.tsx:182 +#: src/view/com/auth/login/ForgotPasswordForm.tsx:192 +#: src/view/com/auth/login/LoginForm.tsx:291 +#: src/view/com/auth/login/SetNewPasswordForm.tsx:187 +#: src/view/com/auth/login/SetNewPasswordForm.tsx:198 +#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:79 +#: src/view/com/modals/ChangePassword.tsx:251 +#: src/view/com/modals/ChangePassword.tsx:253 +msgid "Next" +msgstr "Seuraava" + +#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103 +msgctxt "action" +msgid "Next" +msgstr "Seuraava" + +#: src/view/com/lightbox/Lightbox.web.tsx:149 +msgid "Next image" +msgstr "Seuraava kuva" + +#: src/view/screens/PreferencesFollowingFeed.tsx:129 +#: src/view/screens/PreferencesFollowingFeed.tsx:200 +#: src/view/screens/PreferencesFollowingFeed.tsx:235 +#: src/view/screens/PreferencesFollowingFeed.tsx:272 +#: src/view/screens/PreferencesThreads.tsx:106 +#: src/view/screens/PreferencesThreads.tsx:129 +msgid "No" +msgstr "Ei" + +#: src/view/screens/ProfileFeed.tsx:584 +#: src/view/screens/ProfileList.tsx:754 +msgid "No description" +msgstr "Ei kuvausta" + +#: src/view/com/profile/ProfileHeader.tsx:170 +msgid "No longer following {0}" +msgstr "Et enää seuraa käyttäjää {0}" + +#: src/view/com/notifications/Feed.tsx:109 +msgid "No notifications yet!" +msgstr "Ei vielä ilmoituksia!" + +#: src/view/com/composer/text-input/mobile/Autocomplete.tsx:97 +#: src/view/com/composer/text-input/web/Autocomplete.tsx:191 +msgid "No result" +msgstr "Ei tuloksia" + +#: src/view/screens/Feeds.tsx:495 +msgid "No results found for \"{query}\"" +msgstr "Ei tuloksia haulle \"{query}\"" + +#: src/view/com/modals/ListAddRemoveUsers.tsx:127 +#: src/view/screens/Search/Search.tsx:281 +#: src/view/screens/Search/Search.tsx:309 +msgid "No results found for {query}" +msgstr "Ei tuloksia haulle {query}" + +#: src/view/com/modals/EmbedConsent.tsx:129 +msgid "No thanks" +msgstr "Ei kiitos" + +#: src/view/com/modals/Threadgate.tsx:82 +msgid "Nobody" +msgstr "Ei ketään" + +#: src/view/com/modals/SelfLabel.tsx:135 +msgid "Not Applicable." +msgstr "Ei sovellettavissa." + +#: src/Navigation.tsx:105 +#: src/view/screens/Profile.tsx:106 +msgid "Not Found" +msgstr "Ei löytynyt" + +#: src/view/com/modals/VerifyEmail.tsx:246 +#: src/view/com/modals/VerifyEmail.tsx:252 +msgid "Not right now" +msgstr "Ei juuri nyt" + +#: src/view/screens/Moderation.tsx:252 +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 "Huomio: Bluesky on avoin ja julkinen verkosto. Tämä asetus rajoittaa vain sisältösi näkyvyyttä Bluesky-sovelluksessa ja -sivustolla, eikä muut sovellukset ehkä kunnioita tässä asetuksissaan. Sisältösi voi silti näkyä uloskirjautuneille käyttäjille muissa sovelluksissa ja verkkosivustoilla." + +#: src/Navigation.tsx:450 +#: src/view/screens/Notifications.tsx:124 +#: src/view/screens/Notifications.tsx:148 +#: src/view/shell/bottom-bar/BottomBar.tsx:205 +#: src/view/shell/desktop/LeftNav.tsx:361 +#: src/view/shell/Drawer.tsx:435 +#: src/view/shell/Drawer.tsx:436 +msgid "Notifications" +msgstr "Ilmoitukset" + +#: src/view/com/modals/SelfLabel.tsx:103 +msgid "Nudity" +msgstr "Alastomuus" + +#: src/view/com/util/ErrorBoundary.tsx:35 +msgid "Oh no!" +msgstr "Voi ei!" + +#: src/screens/Onboarding/StepInterests/index.tsx:128 +msgid "Oh no! Something went wrong." +msgstr "Voi ei! Jokin meni pieleen." + +#: src/view/com/auth/login/PasswordUpdatedForm.tsx:41 +msgid "Okay" +msgstr "Selvä" + +#: src/view/screens/PreferencesThreads.tsx:78 +msgid "Oldest replies first" +msgstr "Vanhimmat vastaukset ensin" + +#: src/view/screens/Settings/index.tsx:234 +msgid "Onboarding reset" +msgstr "Käyttöönoton nollaus" + +#: src/view/com/composer/Composer.tsx:382 +msgid "One or more images is missing alt text." +msgstr "Yksi tai useampi kuva on ilman vaihtoehtoista Alt-tekstiä." + +#: src/view/com/threadgate/WhoCanReply.tsx:100 +msgid "Only {0} can reply." +msgstr "Vain {0} voi vastata." + +#: src/view/screens/AppPasswords.tsx:65 +#: src/view/screens/Profile.tsx:106 +msgid "Oops!" +msgstr "Hups!" + +#: src/screens/Onboarding/StepFinished.tsx:115 +msgid "Open" +msgstr "Avaa" + +#: src/view/screens/Moderation.tsx:75 +msgid "Open content filtering settings" +msgstr "Avaa sisällönsuodatusasetukset" + +#: src/view/com/composer/Composer.tsx:477 +#: src/view/com/composer/Composer.tsx:478 +msgid "Open emoji picker" +msgstr "Avaa emoji-valitsin" + +#: src/view/screens/Settings/index.tsx:712 +msgid "Open links with in-app browser" +msgstr "Avaa linkit sovelluksen sisäisellä selaimella" + +#: src/view/screens/Moderation.tsx:92 +msgid "Open muted words settings" +msgstr "Avaa hiljennettyjen sanojen asetukset" + +#: src/view/com/home/HomeHeaderLayoutMobile.tsx:49 +msgid "Open navigation" +msgstr "Avaa navigointi" + +#: src/view/screens/Settings/index.tsx:804 +msgid "Open storybook page" +msgstr "Avaa storybook-sivu" + +#: src/view/com/util/forms/DropdownButton.tsx:154 +msgid "Opens {numItems} options" +msgstr "Avaa {numItems} asetusta" + +#: src/view/screens/Log.tsx:54 +msgid "Opens additional details for a debug entry" +msgstr "Avaa debug lisätiedot" + +#: src/view/com/notifications/FeedItem.tsx:348 +msgid "Opens an expanded list of users in this notification" +msgstr "Avaa laajennetun listan tämän ilmoituksen käyttäjistä" + +#: src/view/com/composer/photos/OpenCameraBtn.tsx:61 +msgid "Opens camera on device" +msgstr "Avaa laitteen kameran" + +#: src/view/com/composer/Prompt.tsx:25 +msgid "Opens composer" +msgstr "Avaa editorin" + +#: src/view/screens/Settings/index.tsx:595 +msgid "Opens configurable language settings" +msgstr "Avaa mukautettavat kielen asetukset" + +#: src/view/com/composer/photos/SelectPhotoBtn.tsx:44 +msgid "Opens device photo gallery" +msgstr "Avaa laitteen valokuvat" + +#: src/view/com/profile/ProfileHeader.tsx:420 +msgid "Opens editor for profile display name, avatar, background image, and description" +msgstr "Avaa editorin profiilin näyttönimeä, avataria, taustakuvaa ja kuvausta varten" + +#: src/view/screens/Settings/index.tsx:649 +msgid "Opens external embeds settings" +msgstr "Avaa ulkoiset upotusasetukset" + +#: src/view/com/profile/ProfileHeader.tsx:575 +msgid "Opens followers list" +msgstr "Avaa seuraajalistan" + +#: src/view/com/profile/ProfileHeader.tsx:594 +msgid "Opens following list" +msgstr "Avaa seurattavien listan" + +#: src/view/screens/Settings.tsx:412 +#~ msgid "Opens invite code list" +#~ msgstr "" + +#: src/view/com/modals/InviteCodes.tsx:172 +msgid "Opens list of invite codes" +msgstr "Avaa kutsukoodien luettelon" + +#: src/view/screens/Settings/index.tsx:774 +msgid "Opens modal for account deletion confirmation. Requires email code." +msgstr "Avaa tilin poistovahvistuksen. Vaatii sähköpostikoodin." + +#: src/view/com/modals/ChangeHandle.tsx:281 +msgid "Opens modal for using custom domain" +msgstr "Avaa asetukset oman verkkotunnuksen käyttöönottoon" + +#: src/view/screens/Settings/index.tsx:620 +msgid "Opens moderation settings" +msgstr "Avaa moderointiasetukset" + +#: src/view/com/auth/login/LoginForm.tsx:239 +msgid "Opens password reset form" +msgstr "Avaa salasanan palautuslomakkeen" + +#: src/view/com/home/HomeHeaderLayout.web.tsx:60 +#: src/view/screens/Feeds.tsx:356 +msgid "Opens screen to edit Saved Feeds" +msgstr "Avaa näkymän tallennettujen syötteiden muokkaamiseen" + +#: src/view/screens/Settings/index.tsx:576 +msgid "Opens screen with all saved feeds" +msgstr "Avaa näkymän kaikkiin tallennettuihin syötteisiin" + +#: src/view/screens/Settings/index.tsx:676 +msgid "Opens the app password settings page" +msgstr "Avaa sovellussalasanojen asetukset" + +#: src/view/screens/Settings/index.tsx:535 +msgid "Opens the home feed preferences" +msgstr "Avaa aloitussivun asetukset" + +#: src/view/screens/Settings/index.tsx:805 +msgid "Opens the storybook page" +msgstr "Avaa storybook-sivun" + +#: src/view/screens/Settings/index.tsx:793 +msgid "Opens the system log page" +msgstr "Avaa järjestelmän lokisivun" + +#: src/view/screens/Settings/index.tsx:556 +msgid "Opens the threads preferences" +msgstr "Avaa keskusteluasetukset" + +#: src/view/com/util/forms/DropdownButton.tsx:280 +msgid "Option {0} of {numItems}" +msgstr "Asetus {0}/{numItems}" + +#: src/view/com/modals/Threadgate.tsx:89 +msgid "Or combine these options:" +msgstr "Tai yhdistä nämä asetukset:" + +#: src/view/com/auth/login/ChooseAccountForm.tsx:138 +msgid "Other account" +msgstr "Toinen tili" + +#: src/view/com/modals/ServerInput.tsx:88 +#~ msgid "Other service" +#~ msgstr "" + +#: src/view/com/composer/select-language/SelectLangBtn.tsx:91 +msgid "Other..." +msgstr "Muu..." + +#: src/view/screens/NotFound.tsx:45 +msgid "Page not found" +msgstr "Sivua ei löytynyt" + +#: src/view/screens/NotFound.tsx:42 +msgid "Page Not Found" +msgstr "Sivua ei löytynyt" + +#: src/view/com/auth/create/Step1.tsx:214 +#: src/view/com/auth/create/Step1.tsx:224 +#: src/view/com/auth/login/LoginForm.tsx:226 +#: src/view/com/auth/login/SetNewPasswordForm.tsx:161 +#: src/view/com/modals/DeleteAccount.tsx:202 +msgid "Password" +msgstr "Salasana" + +#: src/view/com/auth/login/Login.tsx:157 +msgid "Password updated" +msgstr "Salasana päivitetty" + +#: src/view/com/auth/login/PasswordUpdatedForm.tsx:28 +msgid "Password updated!" +msgstr "Salasana päivitetty!" + +#: src/Navigation.tsx:160 +msgid "People followed by @{0}" +msgstr "Henkilöt, joita @{0} seuraa" + +#: src/Navigation.tsx:153 +msgid "People following @{0}" +msgstr "Henkilöt, jotka seuraavat käyttäjää @{0}" + +#: src/view/com/lightbox/Lightbox.tsx:66 +msgid "Permission to access camera roll is required." +msgstr "Käyttöoikeus valokuviin tarvitaan." + +#: src/view/com/lightbox/Lightbox.tsx:72 +msgid "Permission to access camera roll was denied. Please enable it in your system settings." +msgstr "Lupa valokuviin evättiin. Anna lupa järjestelmäasetuksissa." + +#: src/screens/Onboarding/index.tsx:31 +msgid "Pets" +msgstr "Lemmikit" + +#: src/view/com/auth/create/Step2.tsx:183 +#~ msgid "Phone number" +#~ msgstr "Puhelinnumero" + +#: src/view/com/modals/SelfLabel.tsx:121 +msgid "Pictures meant for adults." +msgstr "Aikuisille tarkoitetut kuvat." + +#: src/view/screens/ProfileFeed.tsx:354 +#: src/view/screens/ProfileList.tsx:581 +msgid "Pin to home" +msgstr "Kiinnitä etusivulle" + +#: src/view/screens/SavedFeeds.tsx:88 +msgid "Pinned Feeds" +msgstr "Kiinnitetyt syötteet" + +#: src/view/com/util/post-embeds/ExternalGifEmbed.tsx:111 +msgid "Play {0}" +msgstr "Toista {0}" + +#: src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx:54 +#: src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx:55 +msgid "Play Video" +msgstr "Toista video" + +#: src/view/com/util/post-embeds/ExternalGifEmbed.tsx:110 +msgid "Plays the GIF" +msgstr "Toistaa GIFin" + +#: src/view/com/auth/create/state.ts:124 +msgid "Please choose your handle." +msgstr "Valitse käyttäjätunnuksesi." + +#: src/view/com/auth/create/state.ts:117 +msgid "Please choose your password." +msgstr "Valitse salasanasi." + +#: src/view/com/auth/create/state.ts:131 +msgid "Please complete the verification captcha." +msgstr "Täydennä varmennus-captcha, ole hyvä." + +#: src/view/com/modals/ChangeEmail.tsx:67 +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 "Vahvista sähköpostiosoitteesi ennen sen vaihtamista. Tämä on väliaikainen vaatimus, kunnes sähköpostin muokkaamisen liittyvät asetukset ovat lisätty ja se poistetaan piakkoin." + +#: src/view/com/modals/AddAppPasswords.tsx:90 +msgid "Please enter a name for your app password. All spaces is not allowed." +msgstr "Anna nimi sovellussalasanalle. Kaikki välilyönnit eivät ole sallittuja." + +#: src/view/com/auth/create/Step2.tsx:206 +#~ msgid "Please enter a phone number that can receive SMS text messages." +#~ msgstr "Anna puhelinnumero, joka voi vastaanottaa tekstiviestejä." + +#: src/view/com/modals/AddAppPasswords.tsx:145 +msgid "Please enter a unique name for this App Password or use our randomly generated one." +msgstr "Anna uniikki nimi tälle sovellussalasanalle tai käytä satunnaisesti luotua." + +#: src/view/com/auth/create/state.ts:170 +#~ msgid "Please enter the code you received by SMS." +#~ msgstr "Anna tekstiviestitse saamasi koodi." + +#: src/view/com/auth/create/Step2.tsx:282 +#~ msgid "Please enter the verification code sent to {phoneNumberFormatted}." +#~ msgstr "Anna numeroon {phoneNumberFormatted} vastaanottamasi vahvistuskoodi." + +#: src/view/com/auth/create/state.ts:103 +msgid "Please enter your email." +msgstr "Anna sähköpostiosoitteesi." + +#: src/view/com/modals/DeleteAccount.tsx:191 +msgid "Please enter your password as well:" +msgstr "Anna myös salasanasi:" + +#: src/view/com/modals/AppealLabel.tsx:72 +#: src/view/com/modals/AppealLabel.tsx:75 +msgid "Please tell us why you think this content warning was incorrectly applied!" +msgstr "Kerro meille, miksi luulet, että tämä sisältövaroitus on sovellettu virheellisesti!" + +#: src/view/com/modals/AppealLabel.tsx:72 +#: src/view/com/modals/AppealLabel.tsx:75 +#~ msgid "Please tell us why you think this decision was incorrect." +#~ msgstr "" + +#: src/view/com/modals/VerifyEmail.tsx:101 +msgid "Please Verify Your Email" +msgstr "Vahvista sähköpostiosoitteesi" + +#: src/view/com/composer/Composer.tsx:222 +msgid "Please wait for your link card to finish loading" +msgstr "Odota, että linkkikortti latautuu kokonaan" + +#: src/screens/Onboarding/index.tsx:37 +msgid "Politics" +msgstr "Politiikka" + +#: src/view/com/modals/SelfLabel.tsx:111 +msgid "Porn" +msgstr "Porno" + +#: src/view/com/composer/Composer.tsx:357 +#: src/view/com/composer/Composer.tsx:365 +msgctxt "action" +msgid "Post" +msgstr "Lähetä" + +#: src/view/com/post-thread/PostThread.tsx:303 +msgctxt "description" +msgid "Post" +msgstr "Viesti" + +#: src/view/com/post-thread/PostThreadItem.tsx:175 +msgid "Post by {0}" +msgstr "Lähettäjä {0}" + +#: src/Navigation.tsx:172 +#: src/Navigation.tsx:179 +#: src/Navigation.tsx:186 +msgid "Post by @{0}" +msgstr "Lähettäjä @{0}" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:90 +msgid "Post deleted" +msgstr "Viesti poistettu" + +#: src/view/com/post-thread/PostThread.tsx:462 +msgid "Post hidden" +msgstr "Viesti piilotettu" + +#: src/view/com/composer/select-language/SelectLangBtn.tsx:87 +msgid "Post language" +msgstr "Lähetyskieli" + +#: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:75 +msgid "Post Languages" +msgstr "Lähetyskielet" + +#: src/view/com/post-thread/PostThread.tsx:514 +msgid "Post not found" +msgstr "Viestiä ei löydy" + +#: src/components/TagMenu/index.tsx:257 +msgid "posts" +msgstr "viestit" + +#: src/view/screens/Profile.tsx:180 +msgid "Posts" +msgstr "Viestit" + +#: src/components/dialogs/MutedWords.tsx:77 +msgid "Posts can be muted based on their text, their tags, or both." +msgstr "Viestejä voidaan hiljentää niiden tekstin, tunnisteiden tai molempien perusteella." + +#: src/view/com/posts/FeedErrorMessage.tsx:64 +msgid "Posts hidden" +msgstr "Piilotetut viestit" + +#: src/view/com/modals/LinkWarning.tsx:46 +msgid "Potentially Misleading Link" +msgstr "Mahdollisesti harhaanjohtava linkki" + +#: src/view/com/lightbox/Lightbox.web.tsx:135 +msgid "Previous image" +msgstr "Edellinen kuva" + +#: src/view/screens/LanguageSettings.tsx:187 +msgid "Primary Language" +msgstr "Ensisijainen kieli" + +#: src/view/screens/PreferencesThreads.tsx:97 +msgid "Prioritize Your Follows" +msgstr "Aseta seurattavat tärkeysjärjestykseen" + +#: src/view/screens/Settings/index.tsx:632 +#: src/view/shell/desktop/RightNav.tsx:72 +msgid "Privacy" +msgstr "Yksityisyys" + +#: src/Navigation.tsx:217 +#: src/view/screens/PrivacyPolicy.tsx:29 +#: src/view/screens/Settings/index.tsx:891 +#: src/view/shell/Drawer.tsx:262 +msgid "Privacy Policy" +msgstr "Yksityisyydensuojakäytäntö" + +#: src/view/com/auth/login/ForgotPasswordForm.tsx:198 +msgid "Processing..." +msgstr "Käsitellään..." + +#: src/view/shell/bottom-bar/BottomBar.tsx:247 +#: src/view/shell/desktop/LeftNav.tsx:415 +#: src/view/shell/Drawer.tsx:70 +#: src/view/shell/Drawer.tsx:546 +#: src/view/shell/Drawer.tsx:547 +msgid "Profile" +msgstr "Profiili" + +#: src/view/com/modals/EditProfile.tsx:128 +msgid "Profile updated" +msgstr "Profiili päivitetty" + +#: src/view/screens/Settings/index.tsx:949 +msgid "Protect your account by verifying your email." +msgstr "Suojaa tilisi vahvistamalla sähköpostiosoitteesi." + +#: src/screens/Onboarding/StepFinished.tsx:101 +msgid "Public" +msgstr "Julkinen" + +#: src/view/screens/ModerationModlists.tsx:61 +msgid "Public, shareable lists of users to mute or block in bulk." +msgstr "Julkinen, jaettava käyttäjäluettelo hiljennettyjen tai estettyjen käyttäjien massamäärityksiä varten." + +#: src/view/screens/Lists.tsx:61 +msgid "Public, shareable lists which can drive feeds." +msgstr "Julkinen, jaettava lista, joka voi ohjata syötteitä." + +#: src/view/com/composer/Composer.tsx:342 +msgid "Publish post" +msgstr "Julkaise viesti" + +#: src/view/com/composer/Composer.tsx:342 +msgid "Publish reply" +msgstr "Julkaise vastaus" + +#: src/view/com/modals/Repost.tsx:65 +msgctxt "action" +msgid "Quote post" +msgstr "Lainaa viestiä" + +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:58 +msgid "Quote post" +msgstr "Lainaa viestiä" + +#: src/view/com/modals/Repost.tsx:70 +msgctxt "action" +msgid "Quote Post" +msgstr "Lainaa viestiä" + +#: src/view/screens/PreferencesThreads.tsx:86 +msgid "Random (aka \"Poster's Roulette\")" +msgstr "Satunnainen (tunnetaan myös nimellä \"Lähettäjän ruletti\")" + +#: src/view/com/modals/EditImage.tsx:236 +msgid "Ratios" +msgstr "Suhdeluvut" + +#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:116 +msgid "Recommended Feeds" +msgstr "Suositellut syötteet" + +#: src/view/com/auth/onboarding/RecommendedFollows.tsx:180 +msgid "Recommended Users" +msgstr "Suositellut käyttäjät" + +#: src/components/dialogs/MutedWords.tsx:249 +#: src/view/com/modals/ListAddRemoveUsers.tsx:264 +#: src/view/com/modals/SelfLabel.tsx:83 +#: src/view/com/modals/UserAddRemoveLists.tsx:219 +#: src/view/com/util/UserAvatar.tsx:285 +#: src/view/com/util/UserBanner.tsx:91 +msgid "Remove" +msgstr "Poista" + +#: src/view/com/feeds/FeedSourceCard.tsx:108 +msgid "Remove {0} from my feeds?" +msgstr "Poistetaanko {0} syötteistäni?" + +#: src/view/com/util/AccountDropdownBtn.tsx:22 +msgid "Remove account" +msgstr "Poista tili" + +#: src/view/com/posts/FeedErrorMessage.tsx:131 +#: src/view/com/posts/FeedErrorMessage.tsx:166 +msgid "Remove feed" +msgstr "Poista syöte" + +#: src/view/com/feeds/FeedSourceCard.tsx:107 +#: src/view/com/feeds/FeedSourceCard.tsx:169 +#: src/view/com/feeds/FeedSourceCard.tsx:174 +#: src/view/com/feeds/FeedSourceCard.tsx:245 +#: src/view/screens/ProfileFeed.tsx:273 +msgid "Remove from my feeds" +msgstr "Poista syötteistäni" + +#: src/view/com/composer/photos/Gallery.tsx:167 +msgid "Remove image" +msgstr "Poista kuva" + +#: src/view/com/composer/ExternalEmbed.tsx:70 +msgid "Remove image preview" +msgstr "Poista kuvan esikatselu" + +#: src/components/dialogs/MutedWords.tsx:294 +msgid "Remove mute word from your list" +msgstr "Poista hiljennetty sana listaltasi" + +#: src/view/com/modals/Repost.tsx:47 +msgid "Remove repost" +msgstr "Poista uudelleenjako" + +#: src/view/com/feeds/FeedSourceCard.tsx:175 +msgid "Remove this feed from my feeds?" +msgstr "Poistetaanko tämä syöte omista syötteistäni?" + +#: src/view/com/posts/FeedErrorMessage.tsx:132 +msgid "Remove this feed from your saved feeds?" +msgstr "Poistetaanko tämä syöte tallennetuista syötteistäsi?" + +#: src/view/com/modals/ListAddRemoveUsers.tsx:199 +#: src/view/com/modals/UserAddRemoveLists.tsx:152 +msgid "Removed from list" +msgstr "Poistettu listalta" + +#: src/view/com/feeds/FeedSourceCard.tsx:113 +#: src/view/com/feeds/FeedSourceCard.tsx:180 +msgid "Removed from my feeds" +msgstr "Poistettu syötteistäni" + +#: src/view/com/composer/ExternalEmbed.tsx:71 +msgid "Removes default thumbnail from {0}" +msgstr "Poistaa {0} oletuskuvakkeen" + +#: src/view/screens/Profile.tsx:181 +msgid "Replies" +msgstr "Vastaukset" + +#: src/view/com/threadgate/WhoCanReply.tsx:98 +msgid "Replies to this thread are disabled" +msgstr "Tähän keskusteluun vastaaminen on estetty" + +#: src/view/com/composer/Composer.tsx:355 +msgctxt "action" +msgid "Reply" +msgstr "Vastaa" + +#: src/view/screens/PreferencesFollowingFeed.tsx:144 +msgid "Reply Filters" +msgstr "Vastaussuodattimet" + +#: src/view/com/post/Post.tsx:167 +#: src/view/com/posts/FeedItem.tsx:287 +msgctxt "description" +msgid "Reply to <0/>" +msgstr "Vastaa käyttäjälle <0/>" + +#: src/view/com/modals/report/Modal.tsx:166 +msgid "Report {collectionName}" +msgstr "Raportoi {collectionName}" + +#: src/view/com/profile/ProfileHeader.tsx:361 +msgid "Report Account" +msgstr "Ilmoita tili" + +#: src/view/screens/ProfileFeed.tsx:293 +msgid "Report feed" +msgstr "Ilmoita syöte" + +#: src/view/screens/ProfileList.tsx:459 +msgid "Report List" +msgstr "Ilmoita luettelo" + +#: src/view/com/modals/report/SendReportButton.tsx:37 +#: src/view/com/util/forms/PostDropdownBtn.tsx:255 +msgid "Report post" +msgstr "Ilmoita viesti" + +#: src/view/com/modals/Repost.tsx:43 +#: src/view/com/modals/Repost.tsx:48 +#: src/view/com/modals/Repost.tsx:53 +#: src/view/com/util/post-ctrls/RepostButton.tsx:61 +msgctxt "action" +msgid "Repost" +msgstr "Uudelleenjaa" + +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:48 +msgid "Repost" +msgstr "Uudelleenjaa" + +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:94 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:105 +msgid "Repost or quote post" +msgstr "Uudelleenjaa tai lainaa viestiä" + +#: src/view/screens/PostRepostedBy.tsx:27 +msgid "Reposted By" +msgstr "Uudelleenjakanut" + +#: src/view/com/posts/FeedItem.tsx:207 +msgid "Reposted by {0}" +msgstr "Uudelleenjakanut {0}" + +#: src/view/com/posts/FeedItem.tsx:224 +msgid "Reposted by <0/>" +msgstr "Uudelleenjakanut <0/>" + +#: src/view/com/notifications/FeedItem.tsx:162 +msgid "reposted your post" +msgstr "uudelleenjakoi viestisi" + +#: src/view/com/post-thread/PostThreadItem.tsx:188 +msgid "Reposts of this post" +msgstr "Tämän viestin uudelleenjulkaisut" + +#: src/view/com/modals/ChangeEmail.tsx:181 +#: src/view/com/modals/ChangeEmail.tsx:183 +msgid "Request Change" +msgstr "Pyydä muutosta" + +#: src/view/com/auth/create/Step2.tsx:219 +#~ msgid "Request code" +#~ msgstr "Pyydä koodia" + +#: src/view/com/modals/ChangePassword.tsx:239 +#: src/view/com/modals/ChangePassword.tsx:241 +msgid "Request Code" +msgstr "Pyydä koodia" + +#: src/view/screens/Settings/index.tsx:456 +msgid "Require alt text before posting" +msgstr "Vaadi vaihtoehtoista ALT-tekstiä ennen julkaisua" + +#: src/view/com/auth/create/Step1.tsx:153 +msgid "Required for this provider" +msgstr "Vaaditaan tälle instanssille" + +#: src/view/com/auth/login/SetNewPasswordForm.tsx:124 +#: src/view/com/auth/login/SetNewPasswordForm.tsx:136 +msgid "Reset code" +msgstr "Nollauskoodi" + +#: src/view/com/modals/ChangePassword.tsx:190 +msgid "Reset Code" +msgstr "Nollauskoodi" + +#: src/view/screens/Settings/index.tsx:824 +msgid "Reset onboarding" +msgstr "Nollaa käyttöönotto" + +#: src/view/screens/Settings/index.tsx:827 +msgid "Reset onboarding state" +msgstr "Nollaa käyttöönoton tila" + +#: src/view/com/auth/login/ForgotPasswordForm.tsx:104 +msgid "Reset password" +msgstr "Nollaa salasana" + +#: src/view/screens/Settings/index.tsx:814 +msgid "Reset preferences" +msgstr "Nollaa asetukset" + +#: src/view/screens/Settings/index.tsx:817 +msgid "Reset preferences state" +msgstr "Nollaa asetusten tila" + +#: src/view/screens/Settings/index.tsx:825 +msgid "Resets the onboarding state" +msgstr "Nollaa käyttöönoton tilan" + +#: src/view/screens/Settings/index.tsx:815 +msgid "Resets the preferences state" +msgstr "Nollaa asetusten tilan" + +#: src/view/com/auth/login/LoginForm.tsx:269 +msgid "Retries login" +msgstr "Yrittää uudelleen kirjautumista" + +#: 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 "Yrittää uudelleen viimeisintä toimintoa, joka epäonnistui" + +#: src/screens/Onboarding/StepInterests/index.tsx:221 +#: src/screens/Onboarding/StepInterests/index.tsx:224 +#: src/view/com/auth/create/CreateAccount.tsx:181 +#: src/view/com/auth/create/CreateAccount.tsx:186 +#: src/view/com/auth/login/LoginForm.tsx:268 +#: src/view/com/auth/login/LoginForm.tsx:271 +#: src/view/com/util/error/ErrorMessage.tsx:55 +#: src/view/com/util/error/ErrorScreen.tsx:72 +msgid "Retry" +msgstr "Yritä uudelleen" + +#: src/view/com/auth/create/Step2.tsx:247 +#~ msgid "Retry." +#~ msgstr "Yritä uudelleen." + +#: src/view/screens/ProfileList.tsx:898 +msgid "Return to previous page" +msgstr "Palaa edelliselle sivulle" + +#: src/view/shell/desktop/RightNav.tsx:55 +#~ msgid "SANDBOX. Posts and accounts are not permanent." +#~ msgstr "HIEKKALAATIKKO. Viestit ja tilit eivät ole pysyviä." + +#: src/view/com/lightbox/Lightbox.tsx:132 +#: src/view/com/modals/CreateOrEditList.tsx:345 +msgctxt "action" +msgid "Save" +msgstr "Tallenna" + +#: src/view/com/modals/BirthDateSettings.tsx:94 +#: src/view/com/modals/BirthDateSettings.tsx:97 +#: src/view/com/modals/ChangeHandle.tsx:173 +#: src/view/com/modals/CreateOrEditList.tsx:337 +#: src/view/com/modals/EditProfile.tsx:224 +#: src/view/screens/ProfileFeed.tsx:346 +msgid "Save" +msgstr "Tallenna" + +#: src/view/com/modals/AltImage.tsx:130 +msgid "Save alt text" +msgstr "Tallenna vaihtoehtoinen ALT-teksti" + +#: src/view/com/modals/EditProfile.tsx:232 +msgid "Save Changes" +msgstr "Tallenna muutokset" + +#: src/view/com/modals/ChangeHandle.tsx:170 +msgid "Save handle change" +msgstr "Tallenna käyttäjätunnuksen muutos" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:144 +msgid "Save image crop" +msgstr "Tallenna kuvan rajaus" + +#: src/view/screens/SavedFeeds.tsx:122 +msgid "Saved Feeds" +msgstr "Tallennetut syötteet" + +#: src/view/com/modals/EditProfile.tsx:225 +msgid "Saves any changes to your profile" +msgstr "Tallentaa kaikki muutokset profiiliisi" + +#: src/view/com/modals/ChangeHandle.tsx:171 +msgid "Saves handle change to {handle}" +msgstr "Tallentaa käyttäjätunnuksen muutoksen muotoon {handle}" + +#: src/screens/Onboarding/index.tsx:36 +msgid "Science" +msgstr "Tiede" + +#: src/view/screens/ProfileList.tsx:854 +msgid "Scroll to top" +msgstr "Vieritä alkuun" + +#: src/Navigation.tsx:440 +#: src/view/com/auth/LoggedOut.tsx:122 +#: 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:668 +#: src/view/screens/Search/Search.tsx:686 +#: src/view/shell/bottom-bar/BottomBar.tsx:159 +#: src/view/shell/desktop/LeftNav.tsx:324 +#: src/view/shell/desktop/Search.tsx:214 +#: src/view/shell/desktop/Search.tsx:223 +#: src/view/shell/Drawer.tsx:362 +#: src/view/shell/Drawer.tsx:363 +msgid "Search" +msgstr "Haku" + +#: src/view/screens/Search/Search.tsx:735 +#: src/view/shell/desktop/Search.tsx:255 +msgid "Search for \"{query}\"" +msgstr "Haku hakusanalla \"{query}\"" + +#: src/components/TagMenu/index.tsx:145 +msgid "Search for all posts by @{authorHandle} with tag {tag}" +msgstr "Etsi kaikki viestit käyttäjältä @{authorHandle} tunnisteella {tag}" + +#: src/components/TagMenu/index.tsx:90 +msgid "Search for all posts with tag {tag}" +msgstr "Etsi kaikki viestit tunnisteella {tag}" + +#: src/view/com/auth/LoggedOut.tsx:104 +#: src/view/com/auth/LoggedOut.tsx:105 +#: src/view/com/modals/ListAddRemoveUsers.tsx:70 +msgid "Search for users" +msgstr "Hae käyttäjiä" + +#: src/view/com/modals/ChangeEmail.tsx:110 +msgid "Security Step Required" +msgstr "Turvatarkistus vaaditaan" + +#: src/components/TagMenu/index.web.tsx:50 +msgid "See {truncatedTag} posts" +msgstr "Näytä {truncatedTag}-viestit" + +#: src/components/TagMenu/index.web.tsx:67 +msgid "See {truncatedTag} posts by user" +msgstr "Näytä käyttäjän {truncatedTag} viestit" + +#: src/components/TagMenu/index.tsx:128 +msgid "See <0>{tag}</0> posts" +msgstr "Näytä <0>{tag}</0>-viestit" + +#: src/components/TagMenu/index.tsx:189 +msgid "See <0>{tag}</0> posts by this user" +msgstr "Näytä tämän käyttäjän <0>{tag}</0>-viestit" + +#: src/view/screens/SavedFeeds.tsx:163 +msgid "See this guide" +msgstr "Katso tämä opas" + +#: src/view/com/auth/HomeLoggedOutCTA.tsx:39 +msgid "See what's next" +msgstr "Katso, mitä seuraavaksi tapahtuu" + +#: src/view/com/util/Selector.tsx:106 +msgid "Select {item}" +msgstr "Valitse {item}" + +#: src/view/com/modals/ServerInput.tsx:75 +#~ msgid "Select Bluesky Social" +#~ msgstr "" + +#: src/view/com/auth/login/Login.tsx:117 +msgid "Select from an existing account" +msgstr "Valitse olemassa olevalta tililtä" + +#: src/view/com/util/Selector.tsx:107 +msgid "Select option {i} of {numItems}" +msgstr "Valitse vaihtoehto {i} / {numItems}" + +#: src/view/com/auth/create/Step1.tsx:103 +#: src/view/com/auth/login/LoginForm.tsx:150 +msgid "Select service" +msgstr "Valitse palvelu" + +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:52 +msgid "Select some accounts below to follow" +msgstr "Valitse alla olevista tileistä jotain seurattavaksi" + +#: src/view/com/auth/server-input/index.tsx:82 +msgid "Select the service that hosts your data." +msgstr "Valitse palvelu, joka hostaa tietojasi." + +#: src/screens/Onboarding/StepModeration/index.tsx:49 +#~ msgid "Select the types of content that you want to see (or not see), and we'll handle the rest." +#~ msgstr "" + +#: src/screens/Onboarding/StepTopicalFeeds.tsx:96 +msgid "Select topical feeds to follow from the list below" +msgstr "Valitse ajankohtaisia syötteitä alla olevasta listasta" + +#: src/screens/Onboarding/StepModeration/index.tsx:75 +msgid "Select what you want to see (or not see), and we’ll handle the rest." +msgstr "Valitse, mitä haluat nähdä (tai olla näkemättä) ja me huolehdimme lopusta." + +#: src/view/screens/LanguageSettings.tsx:281 +msgid "Select which languages you want your subscribed feeds to include. If none are selected, all languages will be shown." +msgstr "Valitse, mitä kieliä haluat tilattujen syötteidesi sisältävän. Jos mitään ei ole valittu, kaikki kielet näytetään." + +#: src/view/screens/LanguageSettings.tsx:98 +msgid "Select your app language for the default text to display in the app" +msgstr "Valitse sovelluksen oletuskieli, joka näytetään sovelluksessa" + +#: src/screens/Onboarding/StepInterests/index.tsx:196 +msgid "Select your interests from the options below" +msgstr "Valitse kiinnostuksenkohteesi alla olevista vaihtoehdoista" + +#: src/view/com/auth/create/Step2.tsx:155 +#~ msgid "Select your phone's country" +#~ msgstr "Valitse puhelinnumerosi maa" + +#: src/view/screens/LanguageSettings.tsx:190 +msgid "Select your preferred language for translations in your feed." +msgstr "Valitse haluamasi kieli käännöksille syötteessäsi." + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:116 +msgid "Select your primary algorithmic feeds" +msgstr "Valitse ensisijaiset algoritmisyötteet" + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:142 +msgid "Select your secondary algorithmic feeds" +msgstr "Valitse toissijaiset algoritmisyötteet" + +#: src/view/com/modals/VerifyEmail.tsx:202 +#: src/view/com/modals/VerifyEmail.tsx:204 +msgid "Send Confirmation Email" +msgstr "Lähetä vahvistussähköposti" + +#: src/view/com/modals/DeleteAccount.tsx:131 +msgid "Send email" +msgstr "Lähetä sähköposti" + +#: src/view/com/modals/DeleteAccount.tsx:144 +msgctxt "action" +msgid "Send Email" +msgstr "Lähetä sähköposti" + +#: src/view/shell/Drawer.tsx:295 +#: src/view/shell/Drawer.tsx:316 +msgid "Send feedback" +msgstr "Lähetä palautetta" + +#: src/view/com/modals/report/SendReportButton.tsx:45 +msgid "Send Report" +msgstr "Lähetä raportti" + +#: src/view/com/modals/DeleteAccount.tsx:133 +msgid "Sends email with confirmation code for account deletion" +msgstr "Lähettää sähköpostin tilin poistamiseen tarvittavan vahvistuskoodin" + +#: src/view/com/auth/server-input/index.tsx:110 +msgid "Server address" +msgstr "Palvelimen osoite" + +#: src/view/com/modals/ContentFilteringSettings.tsx:311 +msgid "Set {value} for {labelGroup} content moderation policy" +msgstr "Aseta {value} {labelGroup} sisällön moderointisäännöksi" + +#: src/view/com/modals/ContentFilteringSettings.tsx:160 +#: src/view/com/modals/ContentFilteringSettings.tsx:179 +msgctxt "action" +msgid "Set Age" +msgstr "Aseta ikä" + +#: src/view/screens/Settings/index.tsx:488 +msgid "Set color theme to dark" +msgstr "Aseta väriteema tummaksi" + +#: src/view/screens/Settings/index.tsx:481 +msgid "Set color theme to light" +msgstr "Aseta väriteema vaaleaksi" + +#: src/view/screens/Settings/index.tsx:475 +msgid "Set color theme to system setting" +msgstr "Aseta väriteema järjestelmäasetuksiin" + +#: src/view/screens/Settings/index.tsx:514 +msgid "Set dark theme to the dark theme" +msgstr "Aseta tumma teema tummaksi" + +#: src/view/screens/Settings/index.tsx:507 +msgid "Set dark theme to the dim theme" +msgstr "Aseta tumma teema hämäräksi" + +#: src/view/com/auth/login/SetNewPasswordForm.tsx:104 +msgid "Set new password" +msgstr "Aseta uusi salasana" + +#: src/view/com/auth/create/Step1.tsx:225 +msgid "Set password" +msgstr "Aseta salasana" + +#: src/view/screens/PreferencesFollowingFeed.tsx:225 +msgid "Set this setting to \"No\" to hide all quote posts from your feed. Reposts will still be visible." +msgstr "Aseta tämä asetus \"Ei\"-tilaan piilottaaksesi kaikki lainaukset syötteestäsi. Uudelleenjulkaisut näkyvät silti." + +#: src/view/screens/PreferencesFollowingFeed.tsx:122 +msgid "Set this setting to \"No\" to hide all replies from your feed." +msgstr "Aseta tämä asetus \"Ei\"-tilaan piilottaaksesi kaikki vastaukset syötteestäsi." + +#: src/view/screens/PreferencesFollowingFeed.tsx:191 +msgid "Set this setting to \"No\" to hide all reposts from your feed." +msgstr "Aseta tämä asetus \"Ei\"-tilaan piilottaaksesi kaikki uudelleenjulkaisut syötteestäsi." + +#: 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 "Aseta tämä asetus \"Kyllä\" tilaan näyttääksesi vastaukset ketjumaisessa näkymässä. Tämä on kokeellinen ominaisuus." + +#: src/view/screens/PreferencesHomeFeed.tsx:261 +#~ msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your following feed. This is an experimental feature." +#~ msgstr "Aseta tämä asetus \"Kyllä\"-tilaan nähdäksesi esimerkkejä tallennetuista syötteistäsi seuraamissasi syötteessäsi. Tämä on kokeellinen ominaisuus." + +#: src/view/screens/PreferencesFollowingFeed.tsx:261 +msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your Following feed. This is an experimental feature." +msgstr "Aseta tämä asetus \"Kyllä\"-tilaan nähdäksesi esimerkkejä tallennetuista syötteistäsi seuraamissasi syötteessäsi. Tämä on kokeellinen ominaisuus." + +#: src/screens/Onboarding/Layout.tsx:50 +msgid "Set up your account" +msgstr "Luo tili" + +#: src/view/com/modals/ChangeHandle.tsx:266 +msgid "Sets Bluesky username" +msgstr "Asettaa Bluesky-käyttäjätunnuksen" + +#: src/view/com/auth/login/ForgotPasswordForm.tsx:157 +msgid "Sets email for password reset" +msgstr "Asettaa sähköpostin salasanan palautusta varten" + +#: src/view/com/auth/login/ForgotPasswordForm.tsx:122 +msgid "Sets hosting provider for password reset" +msgstr "Asettaa palveluntarjoajan salasanan palautusta varten" + +#: src/view/com/auth/create/Step1.tsx:104 +#: src/view/com/auth/login/LoginForm.tsx:151 +msgid "Sets server for the Bluesky client" +msgstr "Asettaa palvelimen Bluesky-ohjelmalle" + +#: src/Navigation.tsx:135 +#: src/view/screens/Settings/index.tsx:294 +#: src/view/shell/desktop/LeftNav.tsx:433 +#: src/view/shell/Drawer.tsx:567 +#: src/view/shell/Drawer.tsx:568 +msgid "Settings" +msgstr "Asetukset" + +#: src/view/com/modals/SelfLabel.tsx:125 +msgid "Sexual activity or erotic nudity." +msgstr "Erotiikka tai muu aikuisviihde." + +#: src/view/com/lightbox/Lightbox.tsx:141 +msgctxt "action" +msgid "Share" +msgstr "Jaa" + +#: src/view/com/profile/ProfileHeader.tsx:295 +#: src/view/com/util/forms/PostDropdownBtn.tsx:184 +#: src/view/screens/ProfileList.tsx:418 +msgid "Share" +msgstr "Jaa" + +#: src/view/screens/ProfileFeed.tsx:305 +msgid "Share feed" +msgstr "Jaa syöte" + +#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:43 +#: src/view/com/modals/ContentFilteringSettings.tsx:266 +#: src/view/com/util/moderation/ContentHider.tsx:107 +#: src/view/com/util/moderation/PostHider.tsx:108 +#: src/view/screens/Settings/index.tsx:344 +msgid "Show" +msgstr "Näytä" + +#: src/view/screens/PreferencesFollowingFeed.tsx:68 +msgid "Show all replies" +msgstr "Näytä kaikki vastaukset" + +#: src/view/com/util/moderation/ScreenHider.tsx:132 +msgid "Show anyway" +msgstr "Näytä silti" + +#: src/view/com/modals/EmbedConsent.tsx:87 +msgid "Show embeds from {0}" +msgstr "Näytä upotukset taholta {0}" + +#: src/view/com/profile/ProfileHeader.tsx:459 +msgid "Show follows similar to {0}" +msgstr "Näytä seurannat samankaltaisilta käyttäjiltä kuin {0}" + +#: src/view/com/post-thread/PostThreadItem.tsx:538 +#: src/view/com/post/Post.tsx:198 +#: src/view/com/posts/FeedItem.tsx:363 +msgid "Show More" +msgstr "Näytä lisää" + +#: src/view/screens/PreferencesFollowingFeed.tsx:258 +msgid "Show Posts from My Feeds" +msgstr "Näytä viestit omista syötteistäni" + +#: src/view/screens/PreferencesFollowingFeed.tsx:222 +msgid "Show Quote Posts" +msgstr "Näytä lainatut viestit" + +#: src/screens/Onboarding/StepFollowingFeed.tsx:118 +msgid "Show quote-posts in Following feed" +msgstr "Näytä lainatut viestit seurattavien syötteessä" + +#: src/screens/Onboarding/StepFollowingFeed.tsx:134 +msgid "Show quotes in Following" +msgstr "Näytä lainaukset seurattavissa" + +#: src/screens/Onboarding/StepFollowingFeed.tsx:94 +msgid "Show re-posts in Following feed" +msgstr "Näytä uudelleenjulkaistut viestit seurattavissa" + +#: src/view/screens/PreferencesFollowingFeed.tsx:119 +msgid "Show Replies" +msgstr "Näytä vastaukset" + +#: src/view/screens/PreferencesThreads.tsx:100 +msgid "Show replies by people you follow before all other replies." +msgstr "Näytä seurattujen henkilöiden vastaukset ennen muita vastauksia." + +#: src/screens/Onboarding/StepFollowingFeed.tsx:86 +msgid "Show replies in Following" +msgstr "Näytä vastaukset seurattavissa" + +#: src/screens/Onboarding/StepFollowingFeed.tsx:70 +msgid "Show replies in Following feed" +msgstr "Näytä vastaukset seurattavissa" + +#: src/view/screens/PreferencesFollowingFeed.tsx:70 +msgid "Show replies with at least {value} {0}" +msgstr "Näytä vastaukset, joissa on vähintään {value} {0}" + +#: src/view/screens/PreferencesFollowingFeed.tsx:188 +msgid "Show Reposts" +msgstr "Näytä uudelleenjulkaisut" + +#: src/screens/Onboarding/StepFollowingFeed.tsx:110 +msgid "Show reposts in Following" +msgstr "Näytä uudelleenjulkaisut seurattavissa" + +#: src/view/com/util/moderation/ContentHider.tsx:67 +#: src/view/com/util/moderation/PostHider.tsx:61 +msgid "Show the content" +msgstr "Näytä sisältö" + +#: src/view/com/notifications/FeedItem.tsx:346 +msgid "Show users" +msgstr "Näytä käyttäjät" + +#: src/view/com/profile/ProfileHeader.tsx:462 +msgid "Shows a list of users similar to this user." +msgstr "Näyttää luettelon käyttäjistä, jotka ovat samankaltaisia kuin tämä käyttäjä." + +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:124 +#: src/view/com/profile/ProfileHeader.tsx:506 +msgid "Shows posts from {0} in your feed" +msgstr "Näyttää viestit käyttäjältä {0} syötteessäsi" + +#: src/view/com/auth/HomeLoggedOutCTA.tsx:70 +#: src/view/com/auth/login/Login.tsx:98 +#: src/view/com/auth/SplashScreen.tsx:79 +#: src/view/shell/bottom-bar/BottomBar.tsx:285 +#: src/view/shell/bottom-bar/BottomBar.tsx:286 +#: src/view/shell/bottom-bar/BottomBar.tsx:288 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:178 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:179 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:181 +#: src/view/shell/NavSignupCard.tsx:58 +#: src/view/shell/NavSignupCard.tsx:59 +msgid "Sign in" +msgstr "Kirjaudu sisään" + +#: src/view/com/auth/HomeLoggedOutCTA.tsx:78 +#: src/view/com/auth/SplashScreen.tsx:82 +#: src/view/com/auth/SplashScreen.web.tsx:91 +msgid "Sign In" +msgstr "Kirjaudu sisään" + +#: src/view/com/auth/login/ChooseAccountForm.tsx:44 +msgid "Sign in as {0}" +msgstr "Kirjaudu sisään nimellä {0}" + +#: src/view/com/auth/login/ChooseAccountForm.tsx:118 +#: src/view/com/auth/login/Login.tsx:116 +msgid "Sign in as..." +msgstr "Kirjaudu sisään nimellä..." + +#: src/view/com/auth/login/LoginForm.tsx:137 +msgid "Sign into" +msgstr "Kirjaudu sisään" + +#: src/view/com/modals/SwitchAccount.tsx:64 +#: src/view/com/modals/SwitchAccount.tsx:69 +#: src/view/screens/Settings/index.tsx:100 +#: src/view/screens/Settings/index.tsx:103 +msgid "Sign out" +msgstr "Kirjaudu ulos" + +#: src/view/shell/bottom-bar/BottomBar.tsx:275 +#: src/view/shell/bottom-bar/BottomBar.tsx:276 +#: src/view/shell/bottom-bar/BottomBar.tsx:278 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:168 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:169 +#: src/view/shell/bottom-bar/BottomBarWeb.tsx:171 +#: src/view/shell/NavSignupCard.tsx:49 +#: src/view/shell/NavSignupCard.tsx:50 +#: src/view/shell/NavSignupCard.tsx:52 +msgid "Sign up" +msgstr "Rekisteröidy" + +#: src/view/shell/NavSignupCard.tsx:42 +msgid "Sign up or sign in to join the conversation" +msgstr "Rekisteröidy tai kirjaudu sisään liittyäksesi keskusteluun" + +#: src/view/com/util/moderation/ScreenHider.tsx:76 +msgid "Sign-in Required" +msgstr "Sisäänkirjautuminen vaaditaan" + +#: src/view/screens/Settings/index.tsx:355 +msgid "Signed in as" +msgstr "Kirjautunut sisään nimellä" + +#: src/view/com/auth/login/ChooseAccountForm.tsx:103 +msgid "Signed in as @{0}" +msgstr "Kirjautunut sisään käyttäjätunnuksella @{0}" + +#: src/view/com/modals/SwitchAccount.tsx:66 +msgid "Signs {0} out of Bluesky" +msgstr "{0} kirjautuu ulos Blueskysta" + +#: src/screens/Onboarding/StepInterests/index.tsx:235 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:195 +#: src/view/com/auth/onboarding/WelcomeMobile.tsx:33 +msgid "Skip" +msgstr "Ohita" + +#: src/screens/Onboarding/StepInterests/index.tsx:232 +msgid "Skip this flow" +msgstr "Ohita tämä vaihe" + +#: src/view/com/auth/create/Step2.tsx:82 +#~ msgid "SMS verification" +#~ msgstr "SMS-varmennus" + +#: src/screens/Onboarding/index.tsx:40 +msgid "Software Dev" +msgstr "Ohjelmistokehitys" + +#: src/view/com/modals/ProfilePreview.tsx:62 +#~ msgid "Something went wrong and we're not sure what." +#~ msgstr "" + +#: src/view/com/modals/Waitlist.tsx:51 +msgid "Something went wrong. Check your email and try again." +msgstr "Jotain meni pieleen. Tarkista sähköpostisi ja yritä uudelleen." + +#: src/App.native.tsx:63 +msgid "Sorry! Your session expired. Please log in again." +msgstr "Pahoittelut! Istuntosi on vanhentunut. Kirjaudu sisään uudelleen." + +#: src/view/screens/PreferencesThreads.tsx:69 +msgid "Sort Replies" +msgstr "Lajittele vastaukset" + +#: src/view/screens/PreferencesThreads.tsx:72 +msgid "Sort replies to the same post by:" +msgstr "Lajittele saman viestin vastaukset seuraavasti:" + +#: src/screens/Onboarding/index.tsx:30 +msgid "Sports" +msgstr "Urheilu" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:122 +msgid "Square" +msgstr "Neliö" + +#: src/view/com/modals/ServerInput.tsx:62 +#~ msgid "Staging" +#~ msgstr "" + +#: src/view/screens/Settings/index.tsx:871 +msgid "Status page" +msgstr "Tilasivu" + +#: src/view/com/auth/create/StepHeader.tsx:22 +msgid "Step {0} of {numSteps}" +msgstr "Vaihe {0}/{numSteps}" + +#: src/view/screens/Settings/index.tsx:274 +msgid "Storage cleared, you need to restart the app now." +msgstr "Tallennustila tyhjennetty, sinun on käynnistettävä sovellus uudelleen." + +#: src/Navigation.tsx:202 +#: src/view/screens/Settings/index.tsx:807 +msgid "Storybook" +msgstr "Storybook" + +#: src/view/com/modals/AppealLabel.tsx:101 +msgid "Submit" +msgstr "Lähetä" + +#: src/view/screens/ProfileList.tsx:608 +msgid "Subscribe" +msgstr "Tilaa" + +#: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:173 +#: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:307 +msgid "Subscribe to the {0} feed" +msgstr "Tilaa {0}-syöte" + +#: src/view/screens/ProfileList.tsx:604 +msgid "Subscribe to this list" +msgstr "Tilaa tämä lista" + +#: src/view/screens/Search/Search.tsx:374 +msgid "Suggested Follows" +msgstr "Ehdotetut seurattavat" + +#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:64 +msgid "Suggested for you" +msgstr "Suositeltua sinulle" + +#: src/view/com/modals/SelfLabel.tsx:95 +msgid "Suggestive" +msgstr "Viittaava" + +#: src/Navigation.tsx:212 +#: src/view/screens/Support.tsx:30 +#: src/view/screens/Support.tsx:33 +msgid "Support" +msgstr "Tuki" + +#: src/view/com/modals/ProfilePreview.tsx:110 +#~ msgid "Swipe up to see more" +#~ msgstr "" + +#: src/view/com/modals/SwitchAccount.tsx:117 +msgid "Switch Account" +msgstr "Vaihda tiliä" + +#: src/view/com/modals/SwitchAccount.tsx:97 +#: src/view/screens/Settings/index.tsx:130 +msgid "Switch to {0}" +msgstr "Vaihda käyttäjään {0}" + +#: src/view/com/modals/SwitchAccount.tsx:98 +#: src/view/screens/Settings/index.tsx:131 +msgid "Switches the account you are logged in to" +msgstr "Vaihtaa sisäänkirjautuneen käyttäjän tilin" + +#: src/view/screens/Settings/index.tsx:472 +msgid "System" +msgstr "Järjestelmä" + +#: src/view/screens/Settings/index.tsx:795 +msgid "System log" +msgstr "Järjestelmäloki" + +#: src/components/dialogs/MutedWords.tsx:288 +msgid "tag" +msgstr "tunniste" + +#: src/components/TagMenu/index.tsx:74 +msgid "Tag menu: {tag}" +msgstr "Tunnistevalikko: {tag}" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:112 +msgid "Tall" +msgstr "Pitkä" + +#: src/view/com/util/images/AutoSizedImage.tsx:70 +msgid "Tap to view fully" +msgstr "Napauta nähdäksesi kokonaan" + +#: src/screens/Onboarding/index.tsx:39 +msgid "Tech" +msgstr "Teknologia" + +#: src/view/shell/desktop/RightNav.tsx:81 +msgid "Terms" +msgstr "Ehdot" + +#: src/Navigation.tsx:222 +#: src/view/screens/Settings/index.tsx:885 +#: src/view/screens/TermsOfService.tsx:29 +#: src/view/shell/Drawer.tsx:256 +msgid "Terms of Service" +msgstr "Käyttöehdot" + +#: src/components/dialogs/MutedWords.tsx:288 +msgid "text" +msgstr "teksti" + +#: src/view/com/modals/AppealLabel.tsx:70 +#: src/view/com/modals/report/InputIssueDetails.tsx:51 +msgid "Text input field" +msgstr "Tekstikenttä" + +#: src/view/com/auth/create/CreateAccount.tsx:94 +msgid "That handle is already taken." +msgstr "Tuo käyttätunnus on jo käytössä." + +#: src/view/com/profile/ProfileHeader.tsx:263 +msgid "The account will be able to interact with you after unblocking." +msgstr "Tili voi olla vuorovaikutuksessa kanssasi, kun estäminen on poistettu." + +#: src/view/screens/CommunityGuidelines.tsx:36 +msgid "The Community Guidelines have been moved to <0/>" +msgstr "Yhteisöohjeet on siirretty kohtaan <0/>" + +#: src/view/screens/CopyrightPolicy.tsx:33 +msgid "The Copyright Policy has been moved to <0/>" +msgstr "Tekijänoikeuskäytäntö on siirretty kohtaan <0/>" + +#: src/screens/Onboarding/Layout.tsx:60 +msgid "The following steps will help customize your Bluesky experience." +msgstr "Seuraavat vaiheet auttavat mukauttamaan Bluesky-kokemustasi." + +#: src/view/com/post-thread/PostThread.tsx:517 +msgid "The post may have been deleted." +msgstr "Viesti saattaa olla poistettu." + +#: src/view/screens/PrivacyPolicy.tsx:33 +msgid "The Privacy Policy has been moved to <0/>" +msgstr "Tietosuojakäytäntö on siirretty kohtaan <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 "Tukilomake on siirretty. Jos tarvitset apua, käy osoitteessa <0/> tai vieraile {HELP_DESK_URL} ottaaksesi meihin yhteyttä." + +#: src/view/screens/TermsOfService.tsx:33 +msgid "The Terms of Service have been moved to" +msgstr "Käyttöehdot on siirretty kohtaan" + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:150 +msgid "There are many feeds to try:" +msgstr "On monia syötteitä kokeiltavaksi:" + +#: src/view/screens/ProfileFeed.tsx:550 +msgid "There was an an issue contacting the server, please check your internet connection and try again." +msgstr "Emme saaneet yhteyttä palvelimeen, tarkista internetyhteytesi ja yritä uudelleen." + +#: src/view/com/posts/FeedErrorMessage.tsx:139 +msgid "There was an an issue removing this feed. Please check your internet connection and try again." +msgstr "Syötteen poistossa on ongelmia. Tarkista internetyhteytesi ja yritä uudelleen." + +#: src/view/screens/ProfileFeed.tsx:210 +msgid "There was an an issue updating your feeds, please check your internet connection and try again." +msgstr "Syötteiden päivittämisessä on ongelmia, tarkista internetyhteytesi ja yritä uudelleen." + +#: src/view/screens/ProfileFeed.tsx:237 +#: src/view/screens/ProfileList.tsx:267 +#: src/view/screens/SavedFeeds.tsx:209 +#: src/view/screens/SavedFeeds.tsx:231 +#: src/view/screens/SavedFeeds.tsx:252 +msgid "There was an issue contacting the server" +msgstr "Yhteydenotto palvelimeen epäonnistui" + +#: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:57 +#: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:66 +#: src/view/com/feeds/FeedSourceCard.tsx:115 +#: src/view/com/feeds/FeedSourceCard.tsx:129 +#: src/view/com/feeds/FeedSourceCard.tsx:183 +msgid "There was an issue contacting your server" +msgstr "Yhteydenotto palvelimeen epäonnistui" + +#: src/view/com/notifications/Feed.tsx:117 +msgid "There was an issue fetching notifications. Tap here to try again." +msgstr "Ongelma ilmoitusten hakemisessa. Napauta tästä yrittääksesi uudelleen." + +#: src/view/com/posts/Feed.tsx:263 +msgid "There was an issue fetching posts. Tap here to try again." +msgstr "Ongelma viestien hakemisessa. Napauta tästä yrittääksesi uudelleen." + +#: src/view/com/lists/ListMembers.tsx:172 +msgid "There was an issue fetching the list. Tap here to try again." +msgstr "Ongelma listan hakemisessa. Napauta tästä yrittääksesi uudelleen." + +#: src/view/com/feeds/ProfileFeedgens.tsx:148 +#: src/view/com/lists/ProfileLists.tsx:155 +msgid "There was an issue fetching your lists. Tap here to try again." +msgstr "Ongelma listojesi hakemisessa. Napauta tästä yrittääksesi uudelleen." + +#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:63 +#: src/view/com/modals/ContentFilteringSettings.tsx:126 +msgid "There was an issue syncing your preferences with the server" +msgstr "Ongelma asetuksiesi synkronoinnissa palvelimelle" + +#: src/view/screens/AppPasswords.tsx:66 +msgid "There was an issue with fetching your app passwords" +msgstr "Sovellussalasanojen hakemisessa tapahtui virhe" + +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:93 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:105 +#: src/view/com/profile/ProfileHeader.tsx:157 +#: src/view/com/profile/ProfileHeader.tsx:178 +#: src/view/com/profile/ProfileHeader.tsx:217 +#: src/view/com/profile/ProfileHeader.tsx:230 +#: src/view/com/profile/ProfileHeader.tsx:250 +#: src/view/com/profile/ProfileHeader.tsx:272 +msgid "There was an issue! {0}" +msgstr "Ilmeni ongelma! {0}" + +#: src/view/screens/ProfileList.tsx:288 +#: src/view/screens/ProfileList.tsx:307 +#: src/view/screens/ProfileList.tsx:329 +#: src/view/screens/ProfileList.tsx:348 +msgid "There was an issue. Please check your internet connection and try again." +msgstr "Ilmeni joku ongelma. Tarkista internet-yhteys ja yritä uudelleen." + +#: src/view/com/util/ErrorBoundary.tsx:36 +msgid "There was an unexpected issue in the application. Please let us know if this happened to you!" +msgstr "Sovelluksessa ilmeni odottamaton ongelma. Kerro meille, jos tämä tapahtui sinulle!" + +#: src/screens/Deactivated.tsx:106 +msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." +msgstr "Blueskyyn on tullut paljon uusia käyttäjiä! Aktivoimme tilisi niin pian kuin mahdollista." + +#: src/view/com/auth/create/Step2.tsx:55 +#~ msgid "There's something wrong with this number. Please choose your country and enter your full phone number!" +#~ msgstr "Tässä numerossa on jotain vikaa. Valitse maasi ja syötä koko puhelinnumerosi!" + +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:138 +msgid "These are popular accounts you might like:" +msgstr "Nämä ovat suosittuja tilejä, joista saatat pitää:" + +#: src/view/com/util/moderation/ScreenHider.tsx:88 +msgid "This {screenDescription} has been flagged:" +msgstr "Tämä {screenDescription} on liputettu:" + +#: src/view/com/util/moderation/ScreenHider.tsx:83 +msgid "This account has requested that users sign in to view their profile." +msgstr "Tämä tili pyytää käyttäjiä kirjautumaan sisään nähdäkseen profiilinsa." + +#: src/view/com/modals/EmbedConsent.tsx:68 +msgid "This content is hosted by {0}. Do you want to enable external media?" +msgstr "Tämä sisältö on hostattu palvelussa {0}. Haluatko sallia ulkoisen median?" + +#: src/view/com/modals/ModerationDetails.tsx:67 +msgid "This content is not available because one of the users involved has blocked the other." +msgstr "Tämä sisältö ei ole saatavilla, koska toinen käyttäjistä on estänyt toisen." + +#: src/view/com/posts/FeedErrorMessage.tsx:108 +msgid "This content is not viewable without a Bluesky account." +msgstr "Tätä sisältöä ei voi katsoa ilman Bluesky-tiliä." + +#: src/view/screens/Settings/ExportCarDialog.tsx:75 +msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost.</0>" +msgstr "Tämä ominaisuus on betavaiheessa. Voit lukea lisää pakettivarastojen vientitoiminnosta <0>tässä blogikirjoituksessa.</0>" + +#: src/view/com/posts/FeedErrorMessage.tsx:114 +msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." +msgstr "Tämä syöte saa tällä hetkellä paljon liikennettä ja on tilapäisesti pois käytöstä. Yritä uudelleen myöhemmin." + +#: src/view/screens/Profile.tsx:420 +#: src/view/screens/ProfileFeed.tsx:476 +#: src/view/screens/ProfileList.tsx:661 +msgid "This feed is empty!" +msgstr "Tämä syöte on tyhjä!" + +#: 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 "Tämä syöte on tyhjä! Sinun on ehkä seurattava useampia käyttäjiä tai säädettävä kieliasetuksiasi." + +#: src/view/com/modals/BirthDateSettings.tsx:61 +msgid "This information is not shared with other users." +msgstr "Tätä tietoa ei jaeta muiden käyttäjien kanssa." + +#: src/view/com/modals/VerifyEmail.tsx:119 +msgid "This is important in case you ever need to change your email or reset your password." +msgstr "Tämä on tärkeää, jos sinun tarvitsee vaihtaa sähköpostiosoitteesi tai palauttaa salasanasi." + +#: src/view/com/modals/LinkWarning.tsx:58 +msgid "This link is taking you to the following website:" +msgstr "Tämä linkki vie sinut tälle verkkosivustolle:" + +#: src/view/screens/ProfileList.tsx:834 +msgid "This list is empty!" +msgstr "Tämä lista on tyhjä!" + +#: src/view/com/modals/AddAppPasswords.tsx:106 +msgid "This name is already in use" +msgstr "Tämä nimi on jo käytössä" + +#: src/view/com/post-thread/PostThreadItem.tsx:125 +msgid "This post has been deleted." +msgstr "Tämä viesti on poistettu." + +#: src/view/com/modals/ModerationDetails.tsx:62 +msgid "This user has blocked you. You cannot view their content." +msgstr "Tämä käyttäjä on estänyt sinut. Et voi nähdä heidän sisältöään." + +#: src/view/com/modals/ModerationDetails.tsx:42 +msgid "This user is included in the <0/> list which you have blocked." +msgstr "Tämä käyttäjä on <0/>-listassa, jonka olet estänyt." + +#: src/view/com/modals/ModerationDetails.tsx:74 +msgid "This user is included in the <0/> list which you have muted." +msgstr "Tämä käyttäjä on <0/>-listassa, jonka olet hiljentänyt." + +#: src/view/com/modals/ModerationDetails.tsx:74 +#~ msgid "This user is included the <0/> list which you have muted." +#~ msgstr "" + +#: src/view/com/modals/SelfLabel.tsx:137 +msgid "This warning is only available for posts with media attached." +msgstr "Tämä varoitus on saatavilla vain viesteille, joihin on liitetty mediatiedosto." + +#: src/components/dialogs/MutedWords.tsx:236 +msgid "This will delete {0} from your muted words. You can always add it back later." +msgstr "Tämä poistaa {0}:n hiljennetyistä sanoistasi. Voit lisätä sen takaisin myöhemmin." + +#: src/view/com/util/forms/PostDropdownBtn.tsx:237 +msgid "This will hide this post from your feeds." +msgstr "Tämä piilottaa tämän viestin syötteistäsi." + +#: src/view/screens/PreferencesThreads.tsx:53 +#: src/view/screens/Settings/index.tsx:565 +msgid "Thread Preferences" +msgstr "Keskusteluketjun asetukset" + +#: src/view/screens/PreferencesThreads.tsx:119 +msgid "Threaded Mode" +msgstr "Ketjumainen näkymä" + +#: src/Navigation.tsx:255 +msgid "Threads Preferences" +msgstr "Keskusteluketjujen asetukset" + +#: src/components/dialogs/MutedWords.tsx:95 +msgid "Toggle between muted word options." +msgstr "Vaihda hiljennysvaihtoehtojen välillä." + +#: src/view/com/util/forms/DropdownButton.tsx:246 +msgid "Toggle dropdown" +msgstr "Vaihda pudotusvalikko" + +#: src/view/com/modals/EditImage.tsx:271 +msgid "Transformations" +msgstr "Muutokset" + +#: src/view/com/post-thread/PostThreadItem.tsx:685 +#: src/view/com/post-thread/PostThreadItem.tsx:687 +#: src/view/com/util/forms/PostDropdownBtn.tsx:156 +msgid "Translate" +msgstr "Käännä" + +#: src/view/com/util/error/ErrorScreen.tsx:82 +msgctxt "action" +msgid "Try again" +msgstr "Yritä uudelleen" + +#: src/view/screens/ProfileList.tsx:506 +msgid "Un-block list" +msgstr "Poista listan esto" + +#: src/view/screens/ProfileList.tsx:491 +msgid "Un-mute list" +msgstr "Poista listan hiljennys" + +#: src/view/com/auth/create/CreateAccount.tsx:58 +#: src/view/com/auth/login/ForgotPasswordForm.tsx:87 +#: src/view/com/auth/login/Login.tsx:76 +#: src/view/com/auth/login/LoginForm.tsx:118 +#: src/view/com/modals/ChangePassword.tsx:70 +msgid "Unable to contact your service. Please check your Internet connection." +msgstr "Yhteys palveluusi ei onnistu. Tarkista internet-yhteytesi." + +#: src/view/com/profile/ProfileHeader.tsx:433 +#: src/view/screens/ProfileList.tsx:590 +msgid "Unblock" +msgstr "Poista esto" + +#: src/view/com/profile/ProfileHeader.tsx:435 +msgctxt "action" +msgid "Unblock" +msgstr "Poista esto" + +#: src/view/com/profile/ProfileHeader.tsx:261 +#: src/view/com/profile/ProfileHeader.tsx:345 +msgid "Unblock Account" +msgstr "Poista tilin esto" + +#: src/view/com/modals/Repost.tsx:42 +#: src/view/com/modals/Repost.tsx:55 +#: src/view/com/util/post-ctrls/RepostButton.tsx:60 +#: src/view/com/util/post-ctrls/RepostButton.web.tsx:48 +msgid "Undo repost" +msgstr "Kumoa uudelleenjako" + +#: src/view/com/profile/FollowButton.tsx:55 +msgctxt "action" +msgid "Unfollow" +msgstr "Lopeta seuraaminen" + +#: src/view/com/profile/ProfileHeader.tsx:485 +msgid "Unfollow {0}" +msgstr "Lopeta seuraaminen {0}" + +#: src/view/com/auth/create/state.ts:262 +msgid "Unfortunately, you do not meet the requirements to create an account." +msgstr "Valitettavasti et täytä tilin luomisen vaatimuksia." + +#: src/view/com/util/post-ctrls/PostCtrls.tsx:182 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:216 +msgid "Unlike" +msgstr "En tykkää" + +#: src/components/TagMenu/index.tsx:253 +#: src/view/screens/ProfileList.tsx:597 +msgid "Unmute" +msgstr "Poista hiljennys" + +#: src/components/TagMenu/index.web.tsx:90 +msgid "Unmute {truncatedTag}" +msgstr "Poista hiljennys {truncatedTag}" + +#: src/view/com/profile/ProfileHeader.tsx:326 +msgid "Unmute Account" +msgstr "Poista tilin hiljennys" + +#: src/components/TagMenu/index.tsx:210 +msgid "Unmute all {tag} posts" +msgstr "Poista hiljennys kaikista {tag}-viesteistä" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:202 +msgid "Unmute thread" +msgstr "Poista keskusteluketjun hiljennys" + +#: src/view/screens/ProfileFeed.tsx:353 +#: src/view/screens/ProfileList.tsx:580 +msgid "Unpin" +msgstr "Poista kiinnitys" + +#: src/view/screens/ProfileList.tsx:474 +msgid "Unpin moderation list" +msgstr "Poista moderointilistan kiinnitys" + +#: src/view/screens/ProfileFeed.tsx:346 +msgid "Unsave" +msgstr "Poista tallennus" + +#: src/view/com/modals/UserAddRemoveLists.tsx:70 +msgid "Update {displayName} in Lists" +msgstr "Päivitä {displayName} listoissa" + +#: src/lib/hooks/useOTAUpdate.ts:15 +msgid "Update Available" +msgstr "Päivitys saatavilla" + +#: src/view/com/auth/login/SetNewPasswordForm.tsx:204 +msgid "Updating..." +msgstr "Päivitetään..." + +#: src/view/com/modals/ChangeHandle.tsx:455 +msgid "Upload a text file to:" +msgstr "Lataa tekstitiedosto kohteeseen:" + +#: src/view/screens/AppPasswords.tsx:195 +msgid "Use app passwords to login to other Bluesky clients without giving full access to your account or password." +msgstr "Käytä sovellussalasanoja kirjautuaksesi muihin Bluesky-sovelluksiin antamatta niille täyttä hallintaa tilillesi tai salasanallesi." + +#: src/view/com/modals/ChangeHandle.tsx:515 +msgid "Use default provider" +msgstr "Käytä oletustoimittajaa" + +#: src/view/com/modals/InAppBrowserConsent.tsx:56 +#: src/view/com/modals/InAppBrowserConsent.tsx:58 +msgid "Use in-app browser" +msgstr "Käytä sovelluksen sisäistä selainta" + +#: src/view/com/modals/InAppBrowserConsent.tsx:66 +#: src/view/com/modals/InAppBrowserConsent.tsx:68 +msgid "Use my default browser" +msgstr "Käytä oletusselaintani" + +#: src/view/com/modals/AddAppPasswords.tsx:155 +msgid "Use this to sign into the other app along with your handle." +msgstr "Käytä tätä kirjautuaksesi toiseen sovellukseen käyttäjätunnuksellasi." + +#: src/view/com/modals/ServerInput.tsx:105 +#~ msgid "Use your domain as your Bluesky client service provider" +#~ msgstr "" + +#: src/view/com/modals/InviteCodes.tsx:200 +msgid "Used by:" +msgstr "Käyttänyt:" + +#: src/view/com/modals/ModerationDetails.tsx:54 +msgid "User Blocked" +msgstr "Käyttäjä estetty" + +#: src/view/com/modals/ModerationDetails.tsx:40 +msgid "User Blocked by List" +msgstr "Käyttäjä estetty listan vuoksi" + +#: src/view/com/modals/ModerationDetails.tsx:60 +msgid "User Blocks You" +msgstr "Käyttäjä on estänyt sinut" + +#: src/view/com/auth/create/Step2.tsx:79 +msgid "User handle" +msgstr "Käyttäjätunnus" + +#: src/view/com/lists/ListCard.tsx:85 +#: src/view/com/modals/UserAddRemoveLists.tsx:198 +msgid "User list by {0}" +msgstr "Käyttäjälistan on tehnyt {0}" + +#: src/view/screens/ProfileList.tsx:762 +msgid "User list by <0/>" +msgstr "Käyttäjälistan on tehnyt <0/>" + +#: src/view/com/lists/ListCard.tsx:83 +#: src/view/com/modals/UserAddRemoveLists.tsx:196 +#: src/view/screens/ProfileList.tsx:760 +msgid "User list by you" +msgstr "Sinun käyttäjälistasi" + +#: src/view/com/modals/CreateOrEditList.tsx:196 +msgid "User list created" +msgstr "Käyttäjälista luotu" + +#: src/view/com/modals/CreateOrEditList.tsx:182 +msgid "User list updated" +msgstr "Käyttäjälista päivitetty" + +#: src/view/screens/Lists.tsx:58 +msgid "User Lists" +msgstr "Käyttäjälistat" + +#: src/view/com/auth/login/LoginForm.tsx:177 +#: src/view/com/auth/login/LoginForm.tsx:195 +msgid "Username or email address" +msgstr "Käyttäjätunnus tai sähköpostiosoite" + +#: src/view/screens/ProfileList.tsx:796 +msgid "Users" +msgstr "Käyttäjät" + +#: src/view/com/threadgate/WhoCanReply.tsx:143 +msgid "users followed by <0/>" +msgstr "käyttäjät, joita <0/> seuraa" + +#: src/view/com/modals/Threadgate.tsx:106 +msgid "Users in \"{0}\"" +msgstr "Käyttäjät ryhmässä \"{0}\"" + +#: src/view/com/auth/create/Step2.tsx:243 +#~ msgid "Verification code" +#~ msgstr "Varmistuskoodi" + +#: src/view/screens/Settings/index.tsx:910 +msgid "Verify email" +msgstr "Varmista sähköposti" + +#: src/view/screens/Settings/index.tsx:935 +msgid "Verify my email" +msgstr "Vahvista sähköpostini" + +#: src/view/screens/Settings/index.tsx:944 +msgid "Verify My Email" +msgstr "Vahvista sähköpostini" + +#: src/view/com/modals/ChangeEmail.tsx:205 +#: src/view/com/modals/ChangeEmail.tsx:207 +msgid "Verify New Email" +msgstr "Vahvista uusi sähköposti" + +#: src/view/com/modals/VerifyEmail.tsx:103 +msgid "Verify Your Email" +msgstr "Vahvista sähköpostisi" + +#: src/screens/Onboarding/index.tsx:42 +msgid "Video Games" +msgstr "Videopelit" + +#: src/view/com/profile/ProfileHeader.tsx:662 +msgid "View {0}'s avatar" +msgstr "Katso {0}:n avatar" + +#: src/view/screens/Log.tsx:52 +msgid "View debug entry" +msgstr "Katso vianmääritystietue" + +#: src/view/com/posts/FeedSlice.tsx:103 +msgid "View full thread" +msgstr "Katso koko keskusteluketju" + +#: src/view/com/posts/FeedErrorMessage.tsx:172 +msgid "View profile" +msgstr "Katso profiilia" + +#: src/view/com/profile/ProfileSubpageHeader.tsx:128 +msgid "View the avatar" +msgstr "Katso avatar" + +#: src/view/com/modals/LinkWarning.tsx:75 +msgid "Visit Site" +msgstr "Vieraile sivustolla" + +#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:42 +#: src/view/com/modals/ContentFilteringSettings.tsx:259 +msgid "Warn" +msgstr "Varoita" + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:134 +msgid "We also think you'll like \"For You\" by Skygaze:" +msgstr "Uskomme myös, että pitäisit Skygazen \"For You\" -syötteestä:" + +#: src/screens/Deactivated.tsx:133 +msgid "We estimate {estimatedTime} until your account is ready." +msgstr "Arvioimme, että tilisi valmistumiseen on {estimatedTime} aikaa." + +#: src/screens/Onboarding/StepFinished.tsx:93 +msgid "We hope you have a wonderful time. Remember, Bluesky is:" +msgstr "Toivomme sinulle ihania hetkiä. Muista, että Bluesky on:" + +#: src/view/com/posts/DiscoverFallbackHeader.tsx:29 +msgid "We ran out of posts from your follows. Here's the latest from <0/>." +msgstr "Emme enää löytäneet viestejä seurattavilta. Tässä on uusin tekijältä <0/>." + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:118 +#~ msgid "We recommend \"For You\" by Skygaze:" +#~ msgstr "" + +#: src/components/dialogs/MutedWords.tsx:161 +msgid "We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." +msgstr "Suosittelemme välttämään yleisiä sanoja, jotka esiintyvät monissa viesteissä. Se voi johtaa siihen, ettei viestejä näytetä." + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124 +msgid "We recommend our \"Discover\" feed:" +msgstr "Suosittelemme \"Tutustu\"-syötettämme:" + +#: src/screens/Onboarding/StepInterests/index.tsx:133 +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 "Yhteyden muodostaminen ei onnistunut. Yritä uudelleen jatkaaksesi tilisi määritystä. Jos ongelma jatkuu, voit ohittaa tämän vaiheen." + +#: src/screens/Deactivated.tsx:137 +msgid "We will let you know when your account is ready." +msgstr "Ilmoitamme sinulle, kun tilisi on valmis." + +#: src/view/com/modals/AppealLabel.tsx:48 +msgid "We'll look into your appeal promptly." +msgstr "Käsittelemme vetoomuksesi pikaisesti." + +#: src/screens/Onboarding/StepInterests/index.tsx:138 +msgid "We'll use this to help customize your experience." +msgstr "Käytämme tätä mukauttaaksemme kokemustasi." + +#: src/view/com/auth/create/CreateAccount.tsx:134 +msgid "We're so excited to have you join us!" +msgstr "Olemme innoissamme, että liityt joukkoomme!" + +#: src/view/screens/ProfileList.tsx:86 +msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." +msgstr "Pahoittelemme, emme saaneet avattua tätä listaa. Jos ongelma jatkuu, ota yhteyttä listan tekijään: @{handleOrDid}." + +#: src/components/dialogs/MutedWords.tsx:182 +msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." +msgstr "Pahoittelemme, emme pystyneet lataamaan hiljennettyjä sanojasi tällä hetkellä. Yritä uudelleen." + +#: src/view/screens/Search/Search.tsx:254 +msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." +msgstr "Pahoittelemme, hakuasi ei voitu suorittaa loppuun. Yritä uudelleen muutaman minuutin kuluttua." + +#: src/view/screens/NotFound.tsx:48 +msgid "We're sorry! We can't find the page you were looking for." +msgstr "Pahoittelut! Emme löydä etsimääsi sivua." + +#: src/view/com/auth/onboarding/WelcomeMobile.tsx:46 +msgid "Welcome to <0>Bluesky</0>" +msgstr "Tervetuloa <0>Bluesky</0>:iin" + +#: src/screens/Onboarding/StepInterests/index.tsx:130 +msgid "What are your interests?" +msgstr "Mitkä ovat kiinnostuksenkohteesi?" + +#: src/view/com/modals/report/Modal.tsx:169 +msgid "What is the issue with this {collectionName}?" +msgstr "Mikä on ongelma tämän {collectionName} kanssa?" + +#: src/view/com/auth/SplashScreen.tsx:59 +#: src/view/com/composer/Composer.tsx:286 +msgid "What's up?" +msgstr "Mitä kuuluu?" + +#: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:78 +msgid "Which languages are used in this post?" +msgstr "Mitä kieliä tässä viestissä käytetään?" + +#: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:77 +msgid "Which languages would you like to see in your algorithmic feeds?" +msgstr "Mitä kieliä haluaisit nähdä algoritmisissä syötteissä?" + +#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:47 +#: src/view/com/modals/Threadgate.tsx:66 +msgid "Who can reply" +msgstr "Kuka voi vastata" + +#: src/view/com/modals/crop-image/CropImage.web.tsx:102 +msgid "Wide" +msgstr "Leveä" + +#: src/view/com/composer/Composer.tsx:422 +msgid "Write post" +msgstr "Kirjoita viesti" + +#: src/view/com/composer/Composer.tsx:285 +#: src/view/com/composer/Prompt.tsx:33 +msgid "Write your reply" +msgstr "Kirjoita vastauksesi" + +#: src/screens/Onboarding/index.tsx:28 +msgid "Writers" +msgstr "Kirjoittajat" + +#: src/view/com/auth/create/Step2.tsx:263 +#~ msgid "XXXXXX" +#~ msgstr "XXXXXX" + +#: src/view/com/composer/select-language/SuggestedLanguage.tsx:77 +#: src/view/screens/PreferencesFollowingFeed.tsx:129 +#: src/view/screens/PreferencesFollowingFeed.tsx:201 +#: src/view/screens/PreferencesFollowingFeed.tsx:236 +#: src/view/screens/PreferencesFollowingFeed.tsx:271 +#: src/view/screens/PreferencesThreads.tsx:106 +#: src/view/screens/PreferencesThreads.tsx:129 +msgid "Yes" +msgstr "Kyllä" + +#: src/screens/Onboarding/StepModeration/index.tsx:46 +#~ msgid "You are in control" +#~ msgstr "" + +#: src/screens/Deactivated.tsx:130 +msgid "You are in line." +msgstr "Olet jonossa." + +#: src/view/com/posts/FollowingEmptyState.tsx:67 +#: src/view/com/posts/FollowingEndOfFeed.tsx:68 +msgid "You can also discover new Custom Feeds to follow." +msgstr "Voit myös selata uusia mukautettuja syötteitä seurattavaksi." + +#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:123 +#~ msgid "You can also try our \"Discover\" algorithm:" +#~ msgstr "" + +#: src/screens/Onboarding/StepFollowingFeed.tsx:142 +msgid "You can change these settings later." +msgstr "Voit muuttaa näitä asetuksia myöhemmin." + +#: src/view/com/auth/login/Login.tsx:158 +#: src/view/com/auth/login/PasswordUpdatedForm.tsx:31 +msgid "You can now sign in with your new password." +msgstr "Voit nyt kirjautua sisään uudella salasanallasi." + +#: src/view/com/modals/InviteCodes.tsx:66 +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 "Sinulla ei ole vielä kutsukoodia! Lähetämme sinulle sellaisen, kun olet ollut Bluesky-palvelussa hieman pidempään." + +#: src/view/screens/SavedFeeds.tsx:102 +msgid "You don't have any pinned feeds." +msgstr "Sinulla ei ole kiinnitettyjä syötteitä." + +#: src/view/screens/Feeds.tsx:452 +msgid "You don't have any saved feeds!" +msgstr "Sinulla ei ole tallennettuja syötteitä!" + +#: src/view/screens/SavedFeeds.tsx:135 +msgid "You don't have any saved feeds." +msgstr "Sinulla ei ole tallennettuja syötteitä." + +#: src/view/com/post-thread/PostThread.tsx:465 +msgid "You have blocked the author or you have been blocked by the author." +msgstr "Olet estänyt tekijän tai sinut on estetty tekijän toimesta." + +#: src/view/com/modals/ModerationDetails.tsx:56 +msgid "You have blocked this user. You cannot view their content." +msgstr "Olet estänyt tämän käyttäjän. Et voi nähdä heidän sisältöään." + +#: src/view/com/auth/login/SetNewPasswordForm.tsx:57 +#: src/view/com/auth/login/SetNewPasswordForm.tsx:92 +#: src/view/com/modals/ChangePassword.tsx:87 +#: src/view/com/modals/ChangePassword.tsx:121 +msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." +msgstr "Olet syöttänyt virheellisen koodin. Sen tulisi näyttää muodoltaan XXXXX-XXXXX." + +#: src/view/com/modals/ModerationDetails.tsx:87 +msgid "You have muted this user." +msgstr "Olet hiljentänyt tämän käyttäjän." + +#: src/view/com/feeds/ProfileFeedgens.tsx:136 +msgid "You have no feeds." +msgstr "Sinulla ei ole syötteitä." + +#: src/view/com/lists/MyLists.tsx:89 +#: src/view/com/lists/ProfileLists.tsx:140 +msgid "You have no lists." +msgstr "Sinulla ei ole listoja." + +#: src/view/screens/ModerationBlockedAccounts.tsx:132 +msgid "You have not blocked any accounts yet. To block an account, go to their profile and selected \"Block account\" from the menu on their account." +msgstr "Et ole vielä estänyt yhtään käyttäjää. Estääksesi käyttäjän, siirry heidän profiiliinsa ja valitse \"Estä käyttäjä\"-vaihtoehto heidän tilinsä valikosta." + +#: src/view/screens/AppPasswords.tsx:87 +msgid "You have not created any app passwords yet. You can create one by pressing the button below." +msgstr "Et ole vielä luonut yhtään sovelluksen salasanaa. Voit luoda sellaisen painamalla alla olevaa painiketta." + +#: src/view/screens/ModerationMutedAccounts.tsx:131 +msgid "You have not muted any accounts yet. To mute an account, go to their profile and selected \"Mute account\" from the menu on their account." +msgstr "Et ole vielä hiljentänyt yhtään käyttäjää. Hiljentääksesi käyttäjän, siirry heidän profiiliinsa ja valitse \"Hiljennä käyttäjä\"-vaihtoehto heidän tilinsä valikosta." + +#: src/components/dialogs/MutedWords.tsx:202 +msgid "You haven't muted any words or tags yet" +msgstr "Et ole vielä hiljentänyt yhtään sanaa tai tunnistetta" + +#: src/view/com/modals/ContentFilteringSettings.tsx:175 +msgid "You must be 18 or older to enable adult content." +msgstr "Sinun on oltava vähintään 18-vuotias katsoaksesi aikuissisältöä." + +#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:103 +msgid "You must be 18 years or older to enable adult content" +msgstr "Sinun on oltava vähintään 18-vuotias katsoaksesi aikuissisältöä" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:129 +msgid "You will no longer receive notifications for this thread" +msgstr "Et enää saa ilmoituksia tästä keskustelusta" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:132 +msgid "You will now receive notifications for this thread" +msgstr "Saat nyt ilmoituksia tästä keskustelusta" + +#: src/view/com/auth/login/SetNewPasswordForm.tsx:107 +msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password." +msgstr "Saat sähköpostin \"nollauskoodin\". Syötä koodi tähän ja syötä sitten uusi salasanasi." + +#: src/screens/Onboarding/StepModeration/index.tsx:72 +msgid "You're in control" +msgstr "Sinulla on ohjat" + +#: src/screens/Deactivated.tsx:87 +#: src/screens/Deactivated.tsx:88 +#: src/screens/Deactivated.tsx:103 +msgid "You're in line" +msgstr "Olet jonossa" + +#: src/screens/Onboarding/StepFinished.tsx:90 +msgid "You're ready to go!" +msgstr "Olet valmis aloittamaan!" + +#: src/view/com/posts/FollowingEndOfFeed.tsx:48 +msgid "You've reached the end of your feed! Find some more accounts to follow." +msgstr "Olet saavuttanut syötteesi lopun! Etsi lisää käyttäjiä seurattavaksi." + +#: src/view/com/auth/create/Step1.tsx:74 +msgid "Your account" +msgstr "Tilisi" + +#: src/view/com/modals/DeleteAccount.tsx:67 +msgid "Your account has been deleted" +msgstr "Tilisi on poistettu" + +#: src/view/screens/Settings/ExportCarDialog.tsx:47 +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 "Tilisi arkisto, joka sisältää kaikki julkiset tietueet, voidaan ladata \"CAR\"-tiedostona. Tämä tiedosto ei sisällä upotettuja mediaelementtejä, kuten kuvia, tai yksityisiä tietojasi, jotka on haettava erikseen." + +#: src/view/com/auth/create/Step1.tsx:238 +msgid "Your birth date" +msgstr "Syntymäaikasi" + +#: src/view/com/modals/InAppBrowserConsent.tsx:47 +msgid "Your choice will be saved, but can be changed later in settings." +msgstr "Valintasi tallennetaan, mutta sitä voit muuttaa myöhemmin asetuksissa." + +#: src/screens/Onboarding/StepFollowingFeed.tsx:61 +msgid "Your default feed is \"Following\"" +msgstr "Oletussyötteesi on \"Following\"" + +#: src/view/com/auth/create/state.ts:110 +#: src/view/com/auth/login/ForgotPasswordForm.tsx:70 +#: src/view/com/modals/ChangePassword.tsx:54 +msgid "Your email appears to be invalid." +msgstr "Sähköpostiosoitteesi näyttää olevan virheellinen." + +#: src/view/com/modals/Waitlist.tsx:109 +msgid "Your email has been saved! We'll be in touch soon." +msgstr "Sähköpostiosoitteesi on tallennettu! Olemme pian yhteydessä." + +#: src/view/com/modals/ChangeEmail.tsx:125 +msgid "Your email has been updated but not verified. As a next step, please verify your new email." +msgstr "Sähköpostiosoitteesi on päivitetty, mutta sitä ei ole vielä vahvistettu. Seuraavana vaiheena vahvista uusi sähköpostiosoitteesi." + +#: src/view/com/modals/VerifyEmail.tsx:114 +msgid "Your email has not yet been verified. This is an important security step which we recommend." +msgstr "Sähköpostiosoitettasi ei ole vielä vahvistettu. Tämä on tärkeä turvatoimi, jonka suosittelemme suorittamaan." + +#: src/view/com/posts/FollowingEmptyState.tsx:47 +msgid "Your following feed is empty! Follow more users to see what's happening." +msgstr "Seuraamiesi syöte on tyhjä! Seuraa lisää käyttäjiä nähdäksesi, mitä tapahtuu." + +#: src/view/com/auth/create/Step2.tsx:83 +msgid "Your full handle will be" +msgstr "Käyttäjätunnuksesi tulee olemaan" + +#: src/view/com/modals/ChangeHandle.tsx:270 +msgid "Your full handle will be <0>@{0}</0>" +msgstr "Käyttäjätunnuksesi tulee olemaan <0>@{0}</0>" + +#: src/view/screens/Settings.tsx:430 +#: src/view/shell/desktop/RightNav.tsx:137 +#: src/view/shell/Drawer.tsx:660 +#~ msgid "Your invite codes are hidden when logged in using an App Password" +#~ msgstr "" + +#: src/components/dialogs/MutedWords.tsx:173 +msgid "Your muted words" +msgstr "Hiljentämäsi sanat" + +#: src/view/com/modals/ChangePassword.tsx:155 +msgid "Your password has been changed successfully!" +msgstr "Salasanasi on vaihdettu onnistuneesti!" + +#: src/view/com/composer/Composer.tsx:274 +msgid "Your post has been published" +msgstr "Viestisi on julkaistu" + +#: src/screens/Onboarding/StepFinished.tsx:105 +#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:59 +#: src/view/com/auth/onboarding/WelcomeMobile.tsx:59 +msgid "Your posts, likes, and blocks are public. Mutes are private." +msgstr "Julkaisusi, tykkäyksesi ja estosi ovat julkisia. Hiljennykset ovat yksityisiä." + +#: src/view/com/modals/SwitchAccount.tsx:84 +#: src/view/screens/Settings/index.tsx:118 +msgid "Your profile" +msgstr "Profiilisi" + +#: src/view/com/composer/Composer.tsx:273 +msgid "Your reply has been published" +msgstr "Vastauksesi on julkaistu" + +#: src/view/com/auth/create/Step2.tsx:65 +msgid "Your user handle" +msgstr "Käyttäjätunnuksesi" diff --git a/src/locale/locales/fr/messages.po b/src/locale/locales/fr/messages.po index 2351ecb3a..37af8b386 100644 --- a/src/locale/locales/fr/messages.po +++ b/src/locale/locales/fr/messages.po @@ -8,41 +8,19 @@ msgstr "" "Language: fr\n" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: \n" -"Last-Translator: Stanislas Signoud (@signez.fr)\n" -"Language-Team: \n" +"PO-Revision-Date: 2024-03-12 09:00+0000\n" +"Last-Translator: surfdude29\n" +"Language-Team: Stanislas Signoud (@signez.fr), surfdude29\n" "Plural-Forms: \n" #: src/view/com/modals/VerifyEmail.tsx:142 msgid "(no email)" msgstr "(pas d’e-mail)" -#: src/view/shell/desktop/RightNav.tsx:168 -#~ msgid "{0, plural, one {# invite code available} other {# invite codes available}}" -#~ msgstr "{0, plural, one {# code d’invitation disponible} other {# codes d’invitations disponibles}}" - #: src/view/com/profile/ProfileHeader.tsx:593 msgid "{following} following" msgstr "{following} abonnements" -#: src/view/shell/desktop/RightNav.tsx:151 -#~ msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" -#~ msgstr "{invitesAvailable, plural, one {Code d’invitation : # disponible} other {Codes d’invitation : # disponibles}}" - -#: src/view/screens/Settings.tsx:435 -#: src/view/shell/Drawer.tsx:664 -#~ msgid "{invitesAvailable} invite code available" -#~ msgstr "{invitesAvailable} code d’invitation disponible" - -#: src/view/screens/Settings.tsx:437 -#: src/view/shell/Drawer.tsx:666 -#~ msgid "{invitesAvailable} invite codes available" -#~ msgstr "{invitesAvailable} codes d’invitation disponibles" - -#: src/view/screens/Search/Search.tsx:87 -#~ msgid "{message}" -#~ msgstr "{message}" - #: src/view/shell/Drawer.tsx:440 msgid "{numUnreadNotifications} unread" msgstr "{numUnreadNotifications} non lus" @@ -57,7 +35,7 @@ msgstr "<0>{following} </0><1>abonnements</1>" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:30 msgid "<0>Choose your</0><1>Recommended</1><2>Feeds</2>" -msgstr "<0>Choisissez vos</0><1>fils d’actualité</1><2>recommandés</2>" +msgstr "<0>Choisissez vos</0><1>fils d’actu</1><2>recommandés</2>" #: src/view/com/auth/onboarding/RecommendedFollows.tsx:37 msgid "<0>Follow some</0><1>Recommended</1><2>Users</2>" @@ -179,19 +157,19 @@ msgstr "Ajouter une carte de lien" #: src/view/com/composer/Composer.tsx:458 msgid "Add link card:" -msgstr "Ajouter une carte de lien :" +msgstr "Ajouter une carte de lien :" #: src/components/dialogs/MutedWords.tsx:158 msgid "Add mute word for configured settings" -msgstr "" +msgstr "Ajouter un mot masqué pour les paramètres configurés" #: src/components/dialogs/MutedWords.tsx:87 msgid "Add muted words and tags" -msgstr "" +msgstr "Ajouter des mots et des mots-clés masqués" #: src/view/com/modals/ChangeHandle.tsx:417 msgid "Add the following DNS record to your domain:" -msgstr "Ajoutez l’enregistrement DNS suivant à votre domaine :" +msgstr "Ajoutez l’enregistrement DNS suivant à votre domaine :" #: src/view/com/profile/ProfileHeader.tsx:310 msgid "Add to Lists" @@ -217,7 +195,7 @@ msgstr "Ajouté à mes fils d’actu" #: src/view/screens/PreferencesFollowingFeed.tsx:173 msgid "Adjust the number of likes a reply must have to be shown in your feed." -msgstr "Définissez le nombre de likes qu’une réponse doit avoir pour être affichée dans votre fil d’actualité." +msgstr "Définissez le nombre de likes qu’une réponse doit avoir pour être affichée dans votre fil d’actu." #: src/view/com/modals/SelfLabel.tsx:75 msgid "Adult Content" @@ -227,22 +205,18 @@ msgstr "Contenu pour adultes" msgid "Adult content can only be enabled via the Web at <0/>." msgstr "Le contenu pour adultes ne peut être activé que via le Web à <0/>." -#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:78 -#~ msgid "Adult content can only be enabled via the Web at <0>bsky.app</0>." -#~ msgstr "" - #: src/view/screens/Settings/index.tsx:664 msgid "Advanced" msgstr "Avancé" #: src/view/screens/Feeds.tsx:666 msgid "All the feeds you've saved, right in one place." -msgstr "" +msgstr "Tous les fils d’actu que vous avez enregistrés, au même endroit." #: src/view/com/auth/login/ForgotPasswordForm.tsx:221 #: src/view/com/modals/ChangePassword.tsx:168 msgid "Already have a code?" -msgstr "" +msgstr "Avez-vous déjà un code ?" #: src/view/com/auth/login/ChooseAccountForm.tsx:98 msgid "Already signed in as @{0}" @@ -280,7 +254,7 @@ msgstr "et" #: src/screens/Onboarding/index.tsx:32 msgid "Animals" -msgstr "" +msgstr "Animaux" #: src/view/screens/LanguageSettings.tsx:95 msgid "App Language" @@ -302,10 +276,6 @@ msgstr "Les noms de mots de passe d’application doivent comporter au moins 4 c msgid "App password settings" msgstr "Paramètres de mot de passe d’application" -#: src/view/screens/Settings.tsx:650 -#~ msgid "App passwords" -#~ msgstr "Mots de passe d’application" - #: src/Navigation.tsx:239 #: src/view/screens/AppPasswords.tsx:187 #: src/view/screens/Settings/index.tsx:684 @@ -319,7 +289,7 @@ msgstr "Faire appel de l’avertissement sur le contenu" #: src/view/com/modals/AppealLabel.tsx:65 msgid "Appeal Content Warning" -msgstr "Appel de l’avertissement sur le contenu" +msgstr "Faire appel de l’avertissement sur le contenu" #: src/view/com/util/moderation/LabelInfo.tsx:52 msgid "Appeal this decision" @@ -335,28 +305,28 @@ msgstr "Affichage" #: src/view/screens/AppPasswords.tsx:224 msgid "Are you sure you want to delete the app password \"{name}\"?" -msgstr "Êtes-vous sûr de vouloir supprimer le mot de passe de l’application « {name} » ?" +msgstr "Êtes-vous sûr de vouloir supprimer le mot de passe de l’application « {name} » ?" #: src/view/com/composer/Composer.tsx:150 msgid "Are you sure you'd like to discard this draft?" -msgstr "Êtes-vous sûr de vouloir rejeter ce brouillon ?" +msgstr "Êtes-vous sûr de vouloir rejeter ce brouillon ?" #: src/components/dialogs/MutedWords.tsx:282 #: src/view/screens/ProfileList.tsx:365 msgid "Are you sure?" -msgstr "Vous confirmez ?" +msgstr "Vous confirmez ?" #: src/view/com/util/forms/PostDropdownBtn.tsx:322 msgid "Are you sure? This cannot be undone." -msgstr "Vous confirmez ? Cela ne pourra pas être annulé." +msgstr "Vous confirmez ? Cela ne pourra pas être annulé." #: src/view/com/composer/select-language/SuggestedLanguage.tsx:60 msgid "Are you writing in <0>{0}</0>?" -msgstr "" +msgstr "Écrivez-vous en <0>{0}</0> ?" #: src/screens/Onboarding/index.tsx:26 msgid "Art" -msgstr "" +msgstr "Art" #: src/view/com/modals/SelfLabel.tsx:123 msgid "Artistic or non-erotic nudity." @@ -383,7 +353,7 @@ msgstr "Retour" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:136 msgid "Based on your interest in {interestsText}" -msgstr "" +msgstr "En fonction de votre intérêt pour {interestsText}" #: src/view/screens/Settings/index.tsx:523 msgid "Basics" @@ -396,7 +366,7 @@ msgstr "Date de naissance" #: src/view/screens/Settings/index.tsx:340 msgid "Birthday:" -msgstr "Date de naissance :" +msgstr "Date de naissance :" #: src/view/com/profile/ProfileHeader.tsx:239 #: src/view/com/profile/ProfileHeader.tsx:346 @@ -413,7 +383,7 @@ msgstr "Liste de blocage" #: src/view/screens/ProfileList.tsx:316 msgid "Block these accounts?" -msgstr "Bloquer ces comptes ?" +msgstr "Bloquer ces comptes ?" #: src/view/screens/ProfileList.tsx:320 msgid "Block this List" @@ -462,7 +432,7 @@ msgstr "Bluesky" #: src/view/com/auth/server-input/index.tsx:150 msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers." -msgstr "" +msgstr "Bluesky est un réseau ouvert où vous pouvez choisir votre hébergeur. L’auto-hébergement est désormais disponible en version bêta pour les développeurs." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:80 @@ -479,21 +449,13 @@ msgstr "Bluesky est ouvert." msgid "Bluesky is public." msgstr "Bluesky est public." -#: src/view/com/modals/Waitlist.tsx:70 -#~ msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon." -#~ msgstr "Bluesky distribue des invitations pour construire une communauté plus saine. Si personne ne peut vous donner une invitation, vous pouvez vous inscrire sur notre liste d’attente et nous vous en enverrons une bientôt." - #: src/view/screens/Moderation.tsx:245 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 n’affichera pas votre profil et vos messages à des personnes non connectées. Il est possible que d’autres applications n’honorent pas cette demande. Cela ne privatise pas votre compte." - -#: src/view/com/modals/ServerInput.tsx:78 -#~ msgid "Bluesky.Social" -#~ msgstr "Bluesky.Social" +msgstr "Bluesky n’affichera pas votre profil et vos posts à des personnes non connectées. Il est possible que d’autres applications n’honorent pas cette demande. Cela ne privatise pas votre compte." #: src/screens/Onboarding/index.tsx:33 msgid "Books" -msgstr "" +msgstr "Livres" #: src/view/screens/Settings/index.tsx:859 msgid "Build version {0} {1}" @@ -504,10 +466,6 @@ msgstr "Version Build {0} {1}" msgid "Business" msgstr "Affaires" -#: src/view/com/modals/ServerInput.tsx:115 -#~ msgid "Button disabled. Input custom domain to proceed." -#~ msgstr "Bouton désactivé. Saisissez un domaine personnalisé pour continuer." - #: src/view/com/profile/ProfileSubpageHeader.tsx:157 msgid "by —" msgstr "par —" @@ -589,10 +547,6 @@ msgstr "Annuler la citation" msgid "Cancel search" msgstr "Annuler la recherche" -#: src/view/com/modals/Waitlist.tsx:136 -#~ msgid "Cancel waitlist signup" -#~ msgstr "Annuler l’inscription sur la liste d’attente" - #: src/view/screens/Settings/index.tsx:334 msgctxt "action" msgid "Change" @@ -613,19 +567,19 @@ msgstr "Modifier mon e-mail" #: src/view/screens/Settings/index.tsx:732 msgid "Change password" -msgstr "" +msgstr "Modifier le mot de passe" #: src/view/screens/Settings/index.tsx:741 msgid "Change Password" -msgstr "" +msgstr "Modifier le mot de passe" #: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 msgid "Change post language to {0}" -msgstr "" +msgstr "Modifier la langue de post en {0}" #: src/view/screens/Settings/index.tsx:733 msgid "Change your Bluesky password" -msgstr "" +msgstr "Changer votre mot de passe pour Bluesky" #: src/view/com/modals/ChangeEmail.tsx:109 msgid "Change Your Email" @@ -634,11 +588,11 @@ msgstr "Modifier votre e-mail" #: src/screens/Deactivated.tsx:72 #: src/screens/Deactivated.tsx:76 msgid "Check my status" -msgstr "" +msgstr "Vérifier mon statut" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:121 msgid "Check out some recommended feeds. Tap + to add them to your list of pinned feeds." -msgstr "Consultez quelques fils d’actu recommandés. Appuyez sur + pour les ajouter à votre liste de fils d’actualité." +msgstr "Consultez quelques fils d’actu recommandés. Appuyez sur + pour les ajouter à votre liste de fils d’actu." #: src/view/com/auth/onboarding/RecommendedFollows.tsx:185 msgid "Check out some recommended users. Follow them to see similar users." @@ -646,11 +600,11 @@ msgstr "Consultez quelques comptes recommandés. Suivez-les pour voir des person #: src/view/com/modals/DeleteAccount.tsx:169 msgid "Check your inbox for an email with the confirmation code to enter below:" -msgstr "Consultez votre boîte de réception, vous avez du recevoir un e-mail contenant un code de confirmation à saisir ci-dessous :" +msgstr "Consultez votre boîte de réception, vous avez du recevoir un e-mail contenant un code de confirmation à saisir ci-dessous :" #: src/view/com/modals/Threadgate.tsx:72 msgid "Choose \"Everybody\" or \"Nobody\"" -msgstr "Choisir « Tout le monde » ou « Personne »" +msgstr "Choisir « Tout le monde » ou « Personne »" #: src/view/screens/Settings/index.tsx:697 msgid "Choose a new Bluesky username or create" @@ -662,20 +616,16 @@ msgstr "Choisir un service" #: src/screens/Onboarding/StepFinished.tsx:135 msgid "Choose the algorithms that power your custom feeds." -msgstr "" +msgstr "Choisissez les algorithmes qui alimentent vos fils d’actu personnalisés." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:83 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:83 msgid "Choose the algorithms that power your experience with custom feeds." -msgstr "Choisissez les algorithmes qui alimentent votre expérience avec des fils d’actualité personnalisés." - -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 -#~ msgid "Choose your algorithmic feeds" -#~ msgstr "" +msgstr "Choisissez les algorithmes qui alimentent votre expérience avec des fils d’actu personnalisés." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 msgid "Choose your main feeds" -msgstr "" +msgstr "Choisissez vos principaux fils d’actu" #: src/view/com/auth/create/Step1.tsx:196 msgid "Choose your password" @@ -710,25 +660,25 @@ msgstr "cliquez ici" #: src/components/TagMenu/index.web.tsx:138 msgid "Click here to open tag menu for {tag}" -msgstr "" +msgstr "Cliquez ici pour ouvrir le menu de mot-clé pour {tag}" #: src/components/RichText.tsx:191 msgid "Click here to open tag menu for #{tag}" -msgstr "" +msgstr "Cliquez ici pour ouvrir le menu de mot-clé pour #{tag}" #: src/screens/Onboarding/index.tsx:35 msgid "Climate" -msgstr "" +msgstr "Climat" #: src/view/com/modals/ChangePassword.tsx:265 #: src/view/com/modals/ChangePassword.tsx:268 msgid "Close" -msgstr "" +msgstr "Fermer" #: src/components/Dialog/index.web.tsx:84 #: src/components/Dialog/index.web.tsx:198 msgid "Close active dialog" -msgstr "" +msgstr "Fermer le dialogue actif" #: src/view/com/auth/login/PasswordUpdatedForm.tsx:38 msgid "Close alert" @@ -752,7 +702,7 @@ msgstr "Fermer le pied de page de navigation" #: src/components/TagMenu/index.tsx:262 msgid "Close this dialog" -msgstr "" +msgstr "Fermer ce dialogue" #: src/view/shell/index.web.tsx:52 msgid "Closes bottom navigation bar" @@ -776,11 +726,11 @@ msgstr "Réduit la liste des comptes pour une notification donnée" #: src/screens/Onboarding/index.tsx:41 msgid "Comedy" -msgstr "" +msgstr "Comédie" #: src/screens/Onboarding/index.tsx:27 msgid "Comics" -msgstr "" +msgstr "Bandes dessinées" #: src/Navigation.tsx:229 #: src/view/screens/CommunityGuidelines.tsx:32 @@ -789,15 +739,15 @@ msgstr "Directives communautaires" #: src/screens/Onboarding/StepFinished.tsx:148 msgid "Complete onboarding and start using your account" -msgstr "" +msgstr "Terminez le didacticiel et commencez à utiliser votre compte" #: src/view/com/auth/create/Step3.tsx:73 msgid "Complete the challenge" -msgstr "" +msgstr "Compléter le défi" #: src/view/com/composer/Composer.tsx:424 msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" -msgstr "Permet d’écrire des messages de {MAX_GRAPHEME_LENGTH} caractères maximum" +msgstr "Permet d’écrire des posts de {MAX_GRAPHEME_LENGTH} caractères maximum" #: src/view/com/composer/Prompt.tsx:24 msgid "Compose reply" @@ -805,7 +755,7 @@ msgstr "Rédiger une réponse" #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:67 msgid "Configure content filtering setting for category: {0}" -msgstr "" +msgstr "Configurer les paramètres de filtrage de contenu pour la catégorie : {0}" #: src/components/Prompt.tsx:124 #: src/view/com/modals/AppealLabel.tsx:98 @@ -846,10 +796,6 @@ msgstr "Confirmez votre âge pour activer le contenu pour adultes." msgid "Confirmation code" msgstr "Code de confirmation" -#: src/view/com/modals/Waitlist.tsx:120 -#~ msgid "Confirms signing up {email} to the waitlist" -#~ msgstr "Confirme l’inscription de {email} sur la liste d’attente" - #: src/view/com/auth/create/CreateAccount.tsx:193 #: src/view/com/auth/login/LoginForm.tsx:278 msgid "Connecting..." @@ -857,7 +803,7 @@ msgstr "Connexion…" #: src/view/com/auth/create/CreateAccount.tsx:213 msgid "Contact support" -msgstr "" +msgstr "Contacter le support" #: src/view/screens/Moderation.tsx:83 msgid "Content filtering" @@ -900,19 +846,19 @@ msgstr "Continuer" #: src/screens/Onboarding/StepModeration/index.tsx:115 #: src/screens/Onboarding/StepTopicalFeeds.tsx:111 msgid "Continue to next step" -msgstr "" +msgstr "Passer à l’étape suivante" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:167 msgid "Continue to the next step" -msgstr "" +msgstr "Passer à l’étape suivante" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:191 msgid "Continue to the next step without following any accounts" -msgstr "" +msgstr "Passer à l’étape suivante sans suivre aucun compte" #: src/screens/Onboarding/index.tsx:44 msgid "Cooking" -msgstr "" +msgstr "Cuisine" #: src/view/com/modals/AddAppPasswords.tsx:195 #: src/view/com/modals/InviteCodes.tsx:182 @@ -968,10 +914,6 @@ msgstr "Impossible de charger le fil d’actu" msgid "Could not load list" msgstr "Impossible de charger la liste" -#: src/view/com/auth/create/Step2.tsx:91 -#~ msgid "Country" -#~ msgstr "" - #: src/view/com/auth/HomeLoggedOutCTA.tsx:62 #: src/view/com/auth/SplashScreen.tsx:71 #: src/view/com/auth/SplashScreen.web.tsx:81 @@ -1013,12 +955,12 @@ msgstr "Crée une carte avec une miniature. La carte pointe vers {url}" #: src/screens/Onboarding/index.tsx:29 msgid "Culture" -msgstr "" +msgstr "Culture" #: src/view/com/auth/server-input/index.tsx:95 #: src/view/com/auth/server-input/index.tsx:96 msgid "Custom" -msgstr "" +msgstr "Personnalisé" #: src/view/com/modals/ChangeHandle.tsx:389 msgid "Custom domain" @@ -1027,16 +969,12 @@ msgstr "Domaine personnalisé" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 #: src/view/screens/Feeds.tsx:692 msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." -msgstr "" +msgstr "Les fils d’actu personnalisés élaborés par la communauté vous font vivre de nouvelles expériences et vous aident à trouver le contenu que vous aimez." #: src/view/screens/PreferencesExternalEmbeds.tsx:55 msgid "Customize media from external sites." msgstr "Personnaliser les médias provenant de sites externes." -#: src/view/screens/Settings.tsx:687 -#~ msgid "Danger Zone" -#~ msgstr "Zone de danger" - #: src/view/screens/Settings/index.tsx:485 #: src/view/screens/Settings/index.tsx:511 msgid "Dark" @@ -1048,7 +986,7 @@ msgstr "Mode sombre" #: src/view/screens/Settings/index.tsx:498 msgid "Dark Theme" -msgstr "" +msgstr "Thème sombre" #: src/view/screens/Debug.tsx:83 msgid "Debug panel" @@ -1076,13 +1014,9 @@ msgstr "Supprimer la liste" msgid "Delete my account" msgstr "Supprimer mon compte" -#: src/view/screens/Settings.tsx:706 -#~ msgid "Delete my account…" -#~ msgstr "Supprimer mon compte…" - #: src/view/screens/Settings/index.tsx:784 msgid "Delete My Account…" -msgstr "" +msgstr "Supprimer mon compte…" #: src/view/com/util/forms/PostDropdownBtn.tsx:317 #: src/view/com/util/forms/PostDropdownBtn.tsx:326 @@ -1091,7 +1025,7 @@ msgstr "Supprimer le post" #: src/view/com/util/forms/PostDropdownBtn.tsx:321 msgid "Delete this post?" -msgstr "Supprimer ce post ?" +msgstr "Supprimer ce post ?" #: src/view/com/util/post-embeds/QuoteEmbed.tsx:70 msgid "Deleted" @@ -1108,17 +1042,13 @@ msgstr "Post supprimé." msgid "Description" msgstr "Description" -#: src/view/screens/Settings.tsx:760 -#~ msgid "Developer Tools" -#~ msgstr "Outils de dév" - #: src/view/com/composer/Composer.tsx:218 msgid "Did you want to say anything?" -msgstr "Vous vouliez dire quelque chose ?" +msgstr "Vous vouliez dire quelque chose ?" #: src/view/screens/Settings/index.tsx:504 msgid "Dim" -msgstr "" +msgstr "Atténué" #: src/view/com/composer/Composer.tsx:151 msgid "Discard" @@ -1137,13 +1067,9 @@ msgstr "Empêcher les applis de montrer mon compte aux personnes non connectées msgid "Discover new custom feeds" msgstr "Découvrir des fils d’actu personnalisés" -#: src/view/screens/Feeds.tsx:473 -#~ msgid "Discover new feeds" -#~ msgstr "Découvrir de nouveaux fils d’actu" - #: src/view/screens/Feeds.tsx:689 msgid "Discover New Feeds" -msgstr "" +msgstr "Découvrir de nouveaux fils d’actu" #: src/view/com/modals/EditProfile.tsx:192 msgid "Display name" @@ -1155,11 +1081,7 @@ msgstr "Afficher le nom" #: src/view/com/modals/ChangeHandle.tsx:487 msgid "Domain verified!" -msgstr "Domaine vérifié !" - -#: src/view/com/auth/create/Step1.tsx:170 -#~ msgid "Don't have an invite code?" -#~ msgstr "Pas de code d’invitation ?" +msgstr "Domaine vérifié !" #: src/view/com/auth/onboarding/RecommendedFollows.tsx:86 #: src/view/com/modals/EditImage.tsx:333 @@ -1200,20 +1122,20 @@ msgstr "Tapotez deux fois pour vous connecter" #: src/view/screens/Settings/index.tsx:755 msgid "Download Bluesky account data (repository)" -msgstr "" +msgstr "Télécharger les données du compte Bluesky (dépôt)" #: src/view/screens/Settings/ExportCarDialog.tsx:59 #: src/view/screens/Settings/ExportCarDialog.tsx:63 msgid "Download CAR file" -msgstr "" +msgstr "Télécharger le fichier CAR" #: src/view/com/composer/text-input/TextInput.web.tsx:249 msgid "Drop to add images" -msgstr "" +msgstr "Déposer pour ajouter des images" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:111 msgid "Due to Apple policies, adult content can only be enabled on the web after completing sign up." -msgstr "" +msgstr "En raison des politiques d’Apple, le contenu pour adultes ne peut être activé que via le Web une fois l’inscription terminée." #: src/view/com/modals/EditProfile.tsx:185 msgid "e.g. Alice Roberts" @@ -1298,7 +1220,7 @@ msgstr "Modifier votre description de profil" #: src/screens/Onboarding/index.tsx:34 msgid "Education" -msgstr "" +msgstr "Éducation" #: src/view/com/auth/create/Step1.tsx:176 #: src/view/com/auth/login/ForgotPasswordForm.tsx:156 @@ -1326,7 +1248,7 @@ msgstr "Adresse e-mail vérifiée" #: src/view/screens/Settings/index.tsx:312 msgid "Email:" -msgstr "E-mail :" +msgstr "E-mail :" #: src/view/com/modals/EmbedConsent.tsx:113 msgid "Enable {0} only" @@ -1339,7 +1261,7 @@ msgstr "Activer le contenu pour adultes" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:76 #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:77 msgid "Enable adult content in your feeds" -msgstr "" +msgstr "Activer le contenu pour adultes dans vos fils d’actu" #: src/view/com/modals/EmbedConsent.tsx:97 msgid "Enable External Media" @@ -1364,7 +1286,7 @@ msgstr "Entrer un nom pour ce mot de passe d’application" #: src/components/dialogs/MutedWords.tsx:100 #: src/components/dialogs/MutedWords.tsx:101 msgid "Enter a word or tag" -msgstr "" +msgstr "Saisir un mot ou un mot-clé" #: src/view/com/modals/VerifyEmail.tsx:105 msgid "Enter Confirmation Code" @@ -1372,7 +1294,7 @@ msgstr "Entrer un code de confirmation" #: src/view/com/modals/ChangePassword.tsx:151 msgid "Enter the code you received to change your password." -msgstr "" +msgstr "Saisissez le code que vous avez reçu pour modifier votre mot de passe." #: src/view/com/modals/ChangeHandle.tsx:371 msgid "Enter the domain you want to use" @@ -1380,17 +1302,13 @@ msgstr "Entrez le domaine que vous voulez utiliser" #: src/view/com/auth/login/ForgotPasswordForm.tsx:107 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 "Saisissez l’e-mail que vous avez utilisé pour créer votre compte. Nous vous enverrons un « code de réinitialisation » afin changer votre mot de passe." +msgstr "Saisissez l’e-mail que vous avez utilisé pour créer votre compte. Nous vous enverrons un « code de réinitialisation » afin changer votre mot de passe." #: src/view/com/auth/create/Step1.tsx:228 #: src/view/com/modals/BirthDateSettings.tsx:74 msgid "Enter your birth date" msgstr "Saisissez votre date de naissance" -#: src/view/com/modals/Waitlist.tsx:78 -#~ msgid "Enter your email" -#~ msgstr "Entrez votre e-mail" - #: src/view/com/auth/create/Step1.tsx:172 msgid "Enter your email address" msgstr "Entrez votre e-mail" @@ -1403,21 +1321,17 @@ msgstr "Entrez votre nouvel e-mail ci-dessus" msgid "Enter your new email address below." msgstr "Entrez votre nouvelle e-mail ci-dessous." -#: src/view/com/auth/create/Step2.tsx:188 -#~ msgid "Enter your phone number" -#~ msgstr "" - #: src/view/com/auth/login/Login.tsx:99 msgid "Enter your username and password" msgstr "Entrez votre pseudo et votre mot de passe" #: src/view/com/auth/create/Step3.tsx:67 msgid "Error receiving captcha response." -msgstr "" +msgstr "Erreur de réception de la réponse captcha." #: src/view/screens/Search/Search.tsx:110 msgid "Error:" -msgstr "Erreur :" +msgstr "Erreur :" #: src/view/com/modals/Threadgate.tsx:76 msgid "Everybody" @@ -1436,10 +1350,6 @@ msgstr "Sort de la vue de l’image" msgid "Exits inputting search query" msgstr "Sort de la saisie de la recherche" -#: src/view/com/modals/Waitlist.tsx:138 -#~ msgid "Exits signing up for waitlist with {email}" -#~ msgstr "Sort de l’inscription sur la liste d’attente avec {email}" - #: src/view/com/lightbox/Lightbox.web.tsx:163 msgid "Expand alt text" msgstr "Développer le texte alt" @@ -1451,12 +1361,12 @@ msgstr "Développe ou réduit le post complet auquel vous répondez" #: src/view/screens/Settings/index.tsx:753 msgid "Export my data" -msgstr "" +msgstr "Exporter mes données" #: src/view/screens/Settings/ExportCarDialog.tsx:44 #: src/view/screens/Settings/index.tsx:764 msgid "Export My Data" -msgstr "" +msgstr "Exporter mes données" #: src/view/com/modals/EmbedConsent.tsx:64 msgid "External Media" @@ -1507,10 +1417,6 @@ msgstr "Fil d’actu par {0}" msgid "Feed offline" msgstr "Fil d’actu hors ligne" -#: src/view/com/feeds/FeedPage.tsx:143 -#~ msgid "Feed Preferences" -#~ msgstr "Préférences en matière de fil d’actu" - #: src/view/shell/desktop/RightNav.tsx:61 #: src/view/shell/Drawer.tsx:311 msgid "Feedback" @@ -1525,15 +1431,7 @@ msgstr "Feedback" #: src/view/shell/Drawer.tsx:476 #: src/view/shell/Drawer.tsx:477 msgid "Feeds" -msgstr "Fil d’actu" - -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 -#~ msgid "Feeds are created by users and can give you entirely new experiences." -#~ msgstr "" - -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 -#~ msgid "Feeds are created by users and organizations. They offer you varied experiences and suggest content you may like using algorithms." -#~ msgstr "" +msgstr "Fils d’actu" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:57 msgid "Feeds are created by users to curate content. Choose some feeds that you find interesting." @@ -1545,11 +1443,11 @@ msgstr "Les fils d’actu sont des algorithmes personnalisés qui se construisen #: src/screens/Onboarding/StepTopicalFeeds.tsx:76 msgid "Feeds can be topical as well!" -msgstr "" +msgstr "Les fils d’actu peuvent également être thématiques !" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Finalizing" -msgstr "" +msgstr "Finalisation" #: src/view/com/posts/CustomFeedEmptyState.tsx:47 #: src/view/com/posts/FollowingEmptyState.tsx:57 @@ -1571,11 +1469,7 @@ msgstr "Recherche de comptes similaires…" #: src/view/screens/PreferencesFollowingFeed.tsx:111 msgid "Fine-tune the content you see on your Following feed." -msgstr "" - -#: src/view/screens/PreferencesHomeFeed.tsx:111 -#~ msgid "Fine-tune the content you see on your home screen." -#~ msgstr "Affine le contenu affiché sur votre écran d’accueil." +msgstr "Affine le contenu affiché sur votre fil d’actu « Following »." #: src/view/screens/PreferencesThreads.tsx:60 msgid "Fine-tune the discussion threads." @@ -1583,11 +1477,11 @@ msgstr "Affine les fils de discussion." #: src/screens/Onboarding/index.tsx:38 msgid "Fitness" -msgstr "" +msgstr "Fitness" #: src/screens/Onboarding/StepFinished.tsx:131 msgid "Flexible" -msgstr "" +msgstr "Flexible" #: src/view/com/modals/EditImage.tsx:115 msgid "Flip horizontal" @@ -1617,11 +1511,11 @@ msgstr "Suivre {0}" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:179 msgid "Follow All" -msgstr "" +msgstr "Suivre tous" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:174 msgid "Follow selected accounts and continue to the next step" -msgstr "" +msgstr "Suivre les comptes sélectionnés et passer à l’étape suivante" #: src/view/com/auth/onboarding/RecommendedFollows.tsx:64 msgid "Follow some users to get started. We can recommend you more users based on who you find interesting." @@ -1663,7 +1557,7 @@ msgstr "Suit {0}" #: src/view/screens/PreferencesFollowingFeed.tsx:104 #: src/view/screens/Settings/index.tsx:543 msgid "Following Feed Preferences" -msgstr "" +msgstr "Préférences en matière de fil d’actu « Following »" #: src/view/com/profile/ProfileHeader.tsx:546 msgid "Follows you" @@ -1675,7 +1569,7 @@ msgstr "Vous suit" #: src/screens/Onboarding/index.tsx:43 msgid "Food" -msgstr "" +msgstr "Nourriture" #: src/view/com/modals/DeleteAccount.tsx:111 msgid "For security reasons, we'll need to send a confirmation code to your email address." @@ -1701,7 +1595,7 @@ msgstr "Mot de passe oublié" #: src/screens/Hashtag.tsx:108 #: src/screens/Hashtag.tsx:148 msgid "From @{sanitizedAuthor}" -msgstr "" +msgstr "De @{sanitizedAuthor}" #: src/view/com/posts/FeedItem.tsx:189 msgctxt "from-feed" @@ -1734,12 +1628,12 @@ msgstr "Retour" #: src/screens/Onboarding/Layout.tsx:104 #: src/screens/Onboarding/Layout.tsx:193 msgid "Go back to previous step" -msgstr "" +msgstr "Retour à l’étape précédente" #: src/view/screens/Search/Search.tsx:747 #: src/view/shell/desktop/Search.tsx:262 msgid "Go to @{queryMaybeHandle}" -msgstr "" +msgstr "Aller à @{queryMaybeHandle}" #: src/view/com/auth/login/ForgotPasswordForm.tsx:189 #: src/view/com/auth/login/ForgotPasswordForm.tsx:218 @@ -1755,19 +1649,15 @@ msgstr "Pseudo" #: src/Navigation.tsx:270 msgid "Hashtag" -msgstr "" - -#: src/components/RichText.tsx:188 -#~ msgid "Hashtag: {tag}" -#~ msgstr "" +msgstr "Mot-clé" #: src/components/RichText.tsx:190 msgid "Hashtag: #{tag}" -msgstr "" +msgstr "Mot-clé : #{tag}" #: src/view/com/auth/create/CreateAccount.tsx:208 msgid "Having trouble?" -msgstr "" +msgstr "Un souci ?" #: src/view/shell/desktop/RightNav.tsx:90 #: src/view/shell/Drawer.tsx:321 @@ -1776,15 +1666,15 @@ msgstr "Aide" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:132 msgid "Here are some accounts for you to follow" -msgstr "" +msgstr "Voici quelques comptes à suivre" #: src/screens/Onboarding/StepTopicalFeeds.tsx:85 msgid "Here are some popular topical feeds. You can choose to follow as many as you like." -msgstr "" +msgstr "Voici quelques fils d’actu thématiques populaires. Vous pouvez choisir d’en suivre autant que vous le souhaitez." #: src/screens/Onboarding/StepTopicalFeeds.tsx:80 msgid "Here are some topical feeds based on your interests: {interestsText}. You can choose to follow as many as you like." -msgstr "" +msgstr "Voici quelques fils d’actu thématiques basés sur vos centres d’intérêt : {interestsText}. Vous pouvez choisir d’en suivre autant que vous le souhaitez." #: src/view/com/modals/AddAppPasswords.tsx:153 msgid "Here is your app password." @@ -1815,7 +1705,7 @@ msgstr "Cacher ce contenu" #: src/view/com/util/forms/PostDropdownBtn.tsx:280 msgid "Hide this post?" -msgstr "Cacher ce post ?" +msgstr "Cacher ce post ?" #: src/view/com/notifications/FeedItem.tsx:316 msgid "Hide user list" @@ -1853,13 +1743,6 @@ msgstr "Hmm, nous n’arrivons pas à trouver ce fil d’actu. Il a peut-être à msgid "Home" msgstr "Accueil" -#: src/Navigation.tsx:247 -#: src/view/com/pager/FeedsTabBarMobile.tsx:123 -#: src/view/screens/PreferencesHomeFeed.tsx:104 -#: src/view/screens/Settings/index.tsx:543 -#~ msgid "Home Feed Preferences" -#~ msgstr "Préférences de fils d’actu de l’accueil" - #: src/view/com/auth/create/Step1.tsx:75 #: src/view/com/auth/login/ForgotPasswordForm.tsx:120 msgid "Hosting provider" @@ -1867,7 +1750,7 @@ msgstr "Hébergeur" #: src/view/com/modals/InAppBrowserConsent.tsx:44 msgid "How should we open this link?" -msgstr "" +msgstr "Comment ouvrir ce lien ?" #: src/view/com/modals/VerifyEmail.tsx:214 msgid "I have a code" @@ -1891,7 +1774,7 @@ msgstr "Si rien n’est sélectionné, il n’y a pas de restriction d’âge." #: src/view/com/modals/ChangePassword.tsx:146 msgid "If you want to change your password, we will send you a code to verify that this is your account." -msgstr "" +msgstr "Si vous souhaitez modifier votre mot de passe, nous vous enverrons un code pour vérifier qu’il s’agit bien de votre compte." #: src/view/com/util/images/Gallery.tsx:38 msgid "Image" @@ -1916,7 +1799,7 @@ msgstr "Entrez le code de confirmation pour supprimer le compte" #: src/view/com/auth/create/Step1.tsx:177 msgid "Input email for Bluesky account" -msgstr "" +msgstr "Saisir l’email pour le compte Bluesky" #: src/view/com/auth/create/Step1.tsx:151 msgid "Input invite code to proceed" @@ -1934,10 +1817,6 @@ msgstr "Entrez le nouveau mot de passe" msgid "Input password for account deletion" msgstr "Entrez le mot de passe pour la suppression du compte" -#: src/view/com/auth/create/Step2.tsx:196 -#~ msgid "Input phone number for SMS verification" -#~ msgstr "" - #: src/view/com/auth/login/LoginForm.tsx:230 msgid "Input the password tied to {identifier}" msgstr "Entrez le mot de passe associé à {identifier}" @@ -1946,14 +1825,6 @@ msgstr "Entrez le mot de passe associé à {identifier}" msgid "Input the username or email address you used at signup" msgstr "Entrez le pseudo ou l’adresse e-mail que vous avez utilisé lors de l’inscription" -#: src/view/com/auth/create/Step2.tsx:271 -#~ msgid "Input the verification code we have texted to you" -#~ msgstr "" - -#: src/view/com/modals/Waitlist.tsx:90 -#~ msgid "Input your email to get on the Bluesky waitlist" -#~ msgstr "Entrez votre e-mail pour vous inscrire sur la liste d’attente de Bluesky" - #: src/view/com/auth/login/LoginForm.tsx:229 msgid "Input your password" msgstr "Entrez votre mot de passe" @@ -1970,10 +1841,6 @@ msgstr "Enregistrement de post invalide ou non pris en charge" msgid "Invalid username or password" msgstr "Pseudo ou mot de passe incorrect" -#: src/view/screens/Settings.tsx:411 -#~ msgid "Invite" -#~ msgstr "Inviter" - #: src/view/com/modals/InviteCodes.tsx:93 msgid "Invite a Friend" msgstr "Inviter un ami" @@ -1989,41 +1856,24 @@ msgstr "Code d’invitation refusé. Vérifiez que vous l’avez saisi correctem #: src/view/com/modals/InviteCodes.tsx:170 msgid "Invite codes: {0} available" -msgstr "Code d’invitation : {0} disponible" - -#: src/view/shell/Drawer.tsx:645 -#~ msgid "Invite codes: {invitesAvailable} available" -#~ msgstr "Invitations : {invitesAvailable} codes dispo" +msgstr "Code d’invitation : {0} disponible" #: src/view/com/modals/InviteCodes.tsx:169 msgid "Invite codes: 1 available" -msgstr "Invitations : 1 code dispo" +msgstr "Invitations : 1 code dispo" #: src/screens/Onboarding/StepFollowingFeed.tsx:64 msgid "It shows posts from the people you follow as they happen." -msgstr "" +msgstr "Il affiche les posts des personnes que vous suivez au fur et à mesure qu’ils sont publiés." #: src/view/com/auth/HomeLoggedOutCTA.tsx:99 #: src/view/com/auth/SplashScreen.web.tsx:138 msgid "Jobs" msgstr "Emplois" -#: src/view/com/modals/Waitlist.tsx:67 -#~ msgid "Join the waitlist" -#~ msgstr "S’inscrire sur la liste d’attente" - -#: src/view/com/auth/create/Step1.tsx:174 -#: src/view/com/auth/create/Step1.tsx:178 -#~ msgid "Join the waitlist." -#~ msgstr "S’inscrire sur la liste d’attente." - -#: src/view/com/modals/Waitlist.tsx:128 -#~ msgid "Join Waitlist" -#~ msgstr "S’inscrire sur la liste d’attente" - #: src/screens/Onboarding/index.tsx:24 msgid "Journalism" -msgstr "" +msgstr "Journalisme" #: src/view/com/composer/select-language/SelectLangBtn.tsx:104 msgid "Language selection" @@ -2044,7 +1894,7 @@ msgstr "Langues" #: src/view/com/auth/create/StepHeader.tsx:20 msgid "Last step!" -msgstr "Dernière étape !" +msgstr "Dernière étape !" #: src/view/com/util/moderation/ContentHider.tsx:103 msgid "Learn more" @@ -2078,7 +1928,7 @@ msgstr "Quitter Bluesky" #: src/screens/Deactivated.tsx:128 msgid "left to go." -msgstr "" +msgstr "devant vous dans la file." #: src/view/screens/Settings/index.tsx:278 msgid "Legacy storage cleared, you need to restart the app now." @@ -2087,11 +1937,11 @@ msgstr "Stockage ancien effacé, vous devez redémarrer l’application maintena #: src/view/com/auth/login/Login.tsx:128 #: src/view/com/auth/login/Login.tsx:144 msgid "Let's get your password reset!" -msgstr "Réinitialisez votre mot de passe !" +msgstr "Réinitialisez votre mot de passe !" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Let's go!" -msgstr "" +msgstr "Allons-y !" #: src/view/com/util/UserAvatar.tsx:248 #: src/view/com/util/UserBanner.tsx:62 @@ -2117,7 +1967,7 @@ msgstr "Liké par" #: src/view/screens/PostLikedBy.tsx:27 #: src/view/screens/ProfileFeedLikedBy.tsx:27 msgid "Liked By" -msgstr "" +msgstr "Liké par" #: src/view/com/feeds/FeedSourceCard.tsx:279 msgid "Liked by {0} {1}" @@ -2190,7 +2040,7 @@ msgstr "Listes" #: src/view/com/post-thread/PostThread.tsx:333 #: src/view/com/post-thread/PostThread.tsx:341 msgid "Load more posts" -msgstr "Charger plus d’articles" +msgstr "Charger plus de posts" #: src/view/screens/Notifications.tsx:159 msgid "Load new notifications" @@ -2201,16 +2051,12 @@ msgstr "Charger les nouvelles notifications" #: src/view/screens/ProfileFeed.tsx:495 #: src/view/screens/ProfileList.tsx:681 msgid "Load new posts" -msgstr "Charger les nouveaux messages" +msgstr "Charger les nouveaux posts" #: src/view/com/composer/text-input/mobile/Autocomplete.tsx:95 msgid "Loading..." msgstr "Chargement…" -#: src/view/com/modals/ServerInput.tsx:50 -#~ msgid "Local dev server" -#~ msgstr "Serveur de dév local" - #: src/Navigation.tsx:209 msgid "Log" msgstr "Journaux" @@ -2220,7 +2066,7 @@ msgstr "Journaux" #: src/screens/Deactivated.tsx:178 #: src/screens/Deactivated.tsx:181 msgid "Log out" -msgstr "" +msgstr "Déconnexion" #: src/view/screens/Moderation.tsx:155 msgid "Logged-out visibility" @@ -2232,19 +2078,19 @@ msgstr "Se connecter à un compte qui n’est pas listé" #: src/view/com/modals/LinkWarning.tsx:65 msgid "Make sure this is where you intend to go!" -msgstr "Assurez-vous que c’est bien là que vous avez l’intention d’aller !" +msgstr "Assurez-vous que c’est bien là que vous avez l’intention d’aller !" #: src/components/dialogs/MutedWords.tsx:83 msgid "Manage your muted words and tags" -msgstr "" +msgstr "Gérer les mots et les mots-clés masqués" #: src/view/com/auth/create/Step2.tsx:118 msgid "May not be longer than 253 characters" -msgstr "" +msgstr "Ne doit pas dépasser 253 caractères" #: src/view/com/auth/create/Step2.tsx:109 msgid "May only contain letters and numbers" -msgstr "" +msgstr "Ne peut contenir que des lettres et des chiffres" #: src/view/screens/Profile.tsx:182 msgid "Media" @@ -2265,7 +2111,7 @@ msgstr "Menu" #: src/view/com/posts/FeedErrorMessage.tsx:197 msgid "Message from server: {0}" -msgstr "Message du serveur : {0}" +msgstr "Message du serveur : {0}" #: src/Navigation.tsx:117 #: src/view/screens/Moderation.tsx:66 @@ -2326,25 +2172,21 @@ msgstr "Plus de fils d’actu" msgid "More options" msgstr "Plus d’options" -#: src/view/com/util/forms/PostDropdownBtn.tsx:315 -#~ msgid "More post options" -#~ msgstr "Plus d’options de post" - #: src/view/screens/PreferencesThreads.tsx:82 msgid "Most-liked replies first" msgstr "Réponses les plus likées en premier" #: src/view/com/auth/create/Step2.tsx:122 msgid "Must be at least 3 characters" -msgstr "" +msgstr "Doit comporter au moins 3 caractères" #: src/components/TagMenu/index.tsx:249 msgid "Mute" -msgstr "" +msgstr "Masquer" #: src/components/TagMenu/index.web.tsx:105 msgid "Mute {truncatedTag}" -msgstr "" +msgstr "Masquer {truncatedTag}" #: src/view/com/profile/ProfileHeader.tsx:327 msgid "Mute Account" @@ -2356,19 +2198,15 @@ msgstr "Masquer les comptes" #: src/components/TagMenu/index.tsx:209 msgid "Mute all {displayTag} posts" -msgstr "" - -#: src/components/TagMenu/index.tsx:211 -#~ msgid "Mute all {tag} posts" -#~ msgstr "" +msgstr "Masquer tous les posts {displayTag}" #: src/components/dialogs/MutedWords.tsx:149 msgid "Mute in tags only" -msgstr "" +msgstr "Masquer dans les mots-clés uniquement" #: src/components/dialogs/MutedWords.tsx:134 msgid "Mute in text & tags" -msgstr "" +msgstr "Masquer dans le texte et les mots-clés" #: src/view/screens/ProfileList.tsx:491 msgid "Mute list" @@ -2376,7 +2214,7 @@ msgstr "Masquer la liste" #: src/view/screens/ProfileList.tsx:275 msgid "Mute these accounts?" -msgstr "Masquer ces comptes ?" +msgstr "Masquer ces comptes ?" #: src/view/screens/ProfileList.tsx:279 msgid "Mute this List" @@ -2384,11 +2222,11 @@ msgstr "Masquer cette liste" #: src/components/dialogs/MutedWords.tsx:127 msgid "Mute this word in post text and tags" -msgstr "" +msgstr "Masquer ce mot dans le texte du post et les mots-clés" #: src/components/dialogs/MutedWords.tsx:142 msgid "Mute this word in tags only" -msgstr "" +msgstr "Masquer ce mot dans les mots-clés uniquement" #: src/view/com/util/forms/PostDropdownBtn.tsx:251 #: src/view/com/util/forms/PostDropdownBtn.tsx:257 @@ -2398,11 +2236,11 @@ msgstr "Masquer ce fil de discussion" #: src/view/com/util/forms/PostDropdownBtn.tsx:267 #: src/view/com/util/forms/PostDropdownBtn.tsx:269 msgid "Mute words & tags" -msgstr "" +msgstr "Masquer les mots et les mots-clés" #: src/view/com/lists/ListCard.tsx:102 msgid "Muted" -msgstr "" +msgstr "Masqué" #: src/view/screens/Moderation.tsx:128 msgid "Muted accounts" @@ -2415,11 +2253,11 @@ msgstr "Comptes masqués" #: src/view/screens/ModerationMutedAccounts.tsx:115 msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private." -msgstr "Les comptes masqués voient leurs posts supprimés de votre fil d’actualité et de vos notifications. Cette option est totalement privée." +msgstr "Les comptes masqués voient leurs posts supprimés de votre fil d’actu et de vos notifications. Cette option est totalement privée." #: src/view/screens/Moderation.tsx:100 msgid "Muted words & tags" -msgstr "" +msgstr "Les mots et les mots-clés masqués" #: src/view/screens/ProfileList.tsx:277 msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." @@ -2443,7 +2281,7 @@ msgstr "Mes fils d’actu enregistrés" #: src/view/com/auth/server-input/index.tsx:118 msgid "my-server.com" -msgstr "" +msgstr "mon-serveur.fr" #: src/view/com/modals/AddAppPasswords.tsx:179 #: src/view/com/modals/CreateOrEditList.tsx:290 @@ -2456,7 +2294,7 @@ msgstr "Le nom est requis" #: src/screens/Onboarding/index.tsx:25 msgid "Nature" -msgstr "" +msgstr "Nature" #: src/view/com/auth/login/ForgotPasswordForm.tsx:190 #: src/view/com/auth/login/ForgotPasswordForm.tsx:219 @@ -2482,11 +2320,11 @@ msgstr "Ne perdez jamais l’accès à vos followers et à vos données." #: src/screens/Onboarding/StepFinished.tsx:119 msgid "Never lose access to your followers or data." -msgstr "" +msgstr "Ne perdez jamais l’accès à vos followers ou à vos données." #: src/components/dialogs/MutedWords.tsx:293 msgid "Nevermind" -msgstr "" +msgstr "Peu importe" #: src/view/screens/Lists.tsx:76 msgctxt "action" @@ -2507,7 +2345,7 @@ msgstr "Nouveau mot de passe" #: src/view/com/modals/ChangePassword.tsx:215 msgid "New Password" -msgstr "" +msgstr "Nouveau mot de passe" #: src/view/com/feeds/FeedPage.tsx:126 msgctxt "action" @@ -2539,7 +2377,7 @@ msgstr "Réponses les plus récentes en premier" #: src/screens/Onboarding/index.tsx:23 msgid "News" -msgstr "" +msgstr "Actualités" #: src/view/com/auth/create/CreateAccount.tsx:172 #: src/view/com/auth/login/ForgotPasswordForm.tsx:182 @@ -2582,7 +2420,7 @@ msgstr "Ne suit plus {0}" #: src/view/com/notifications/Feed.tsx:109 msgid "No notifications yet!" -msgstr "Pas encore de notifications !" +msgstr "Pas encore de notifications !" #: src/view/com/composer/text-input/mobile/Autocomplete.tsx:97 #: src/view/com/composer/text-input/web/Autocomplete.tsx:191 @@ -2591,11 +2429,11 @@ msgstr "Aucun résultat" #: src/components/Lists.tsx:192 msgid "No results found" -msgstr "" +msgstr "Aucun résultat trouvé" #: src/view/screens/Feeds.tsx:495 msgid "No results found for \"{query}\"" -msgstr "Aucun résultat trouvé pour « {query} »" +msgstr "Aucun résultat trouvé pour « {query} »" #: src/view/com/modals/ListAddRemoveUsers.tsx:127 #: src/view/screens/Search/Search.tsx:281 @@ -2627,7 +2465,7 @@ msgstr "Pas maintenant" #: src/view/screens/Moderation.tsx:252 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 "Remarque : Bluesky est un réseau ouvert et public. Ce paramètre limite uniquement la visibilité de votre contenu sur l’application et le site Web de Bluesky, et d’autres applications peuvent ne pas respecter ce paramètre. Votre contenu peut toujours être montré aux personnes non connectées par d’autres applications et sites Web." +msgstr "Remarque : Bluesky est un réseau ouvert et public. Ce paramètre limite uniquement la visibilité de votre contenu sur l’application et le site Web de Bluesky, et d’autres applications peuvent ne pas respecter ce paramètre. Votre contenu peut toujours être montré aux personnes non connectées par d’autres applications et sites Web." #: src/Navigation.tsx:457 #: src/view/screens/Notifications.tsx:124 @@ -2645,11 +2483,11 @@ msgstr "Nudité" #: src/view/com/util/ErrorBoundary.tsx:35 msgid "Oh no!" -msgstr "Oh non !" +msgstr "Oh non !" #: src/screens/Onboarding/StepInterests/index.tsx:128 msgid "Oh no! Something went wrong." -msgstr "" +msgstr "Oh non ! Il y a eu un problème." #: src/view/com/auth/login/PasswordUpdatedForm.tsx:41 msgid "Okay" @@ -2673,21 +2511,21 @@ msgstr "Seul {0} peut répondre." #: src/components/Lists.tsx:82 msgid "Oops, something went wrong!" -msgstr "" +msgstr "Oups, quelque chose n’a pas marché !" #: src/components/Lists.tsx:188 #: src/view/screens/AppPasswords.tsx:65 #: src/view/screens/Profile.tsx:106 msgid "Oops!" -msgstr "Oups !" +msgstr "Oups !" #: src/screens/Onboarding/StepFinished.tsx:115 msgid "Open" -msgstr "" +msgstr "Ouvrir" #: src/view/screens/Moderation.tsx:75 msgid "Open content filtering settings" -msgstr "" +msgstr "Ouvrir les paramètres de filtrage de contenu" #: src/view/com/composer/Composer.tsx:477 #: src/view/com/composer/Composer.tsx:478 @@ -2696,11 +2534,11 @@ msgstr "Ouvrir le sélecteur d’emoji" #: src/view/screens/Settings/index.tsx:712 msgid "Open links with in-app browser" -msgstr "" +msgstr "Ouvrir des liens avec le navigateur interne à l’appli" #: src/view/screens/Moderation.tsx:92 msgid "Open muted words settings" -msgstr "" +msgstr "Ouvrir les paramètres des mots masqués" #: src/view/com/home/HomeHeaderLayoutMobile.tsx:50 msgid "Open navigation" @@ -2708,7 +2546,7 @@ msgstr "Navigation ouverte" #: src/view/com/util/forms/PostDropdownBtn.tsx:175 msgid "Open post options menu" -msgstr "" +msgstr "Ouvrir le menu d’options du post" #: src/view/screens/Settings/index.tsx:804 msgid "Open storybook page" @@ -2758,10 +2596,6 @@ msgstr "Ouvre la liste des comptes abonnés" msgid "Opens following list" msgstr "Ouvre la liste des abonnements" -#: src/view/screens/Settings.tsx:412 -#~ msgid "Opens invite code list" -#~ msgstr "Ouvre la liste des codes d’invitation" - #: src/view/com/modals/InviteCodes.tsx:172 msgid "Opens list of invite codes" msgstr "Ouvre la liste des codes d’invitation" @@ -2817,16 +2651,12 @@ msgstr "Option {0} sur {numItems}" #: src/view/com/modals/Threadgate.tsx:89 msgid "Or combine these options:" -msgstr "Ou une combinaison de ces options :" +msgstr "Ou une combinaison de ces options :" #: src/view/com/auth/login/ChooseAccountForm.tsx:138 msgid "Other account" msgstr "Autre compte" -#: src/view/com/modals/ServerInput.tsx:88 -#~ msgid "Other service" -#~ msgstr "Autre service" - #: src/view/com/composer/select-language/SelectLangBtn.tsx:91 msgid "Other..." msgstr "Autre…" @@ -2838,7 +2668,7 @@ msgstr "Page introuvable" #: src/view/screens/NotFound.tsx:42 msgid "Page Not Found" -msgstr "" +msgstr "Page introuvable" #: src/view/com/auth/create/Step1.tsx:191 #: src/view/com/auth/create/Step1.tsx:201 @@ -2854,7 +2684,7 @@ msgstr "Mise à jour du mot de passe" #: src/view/com/auth/login/PasswordUpdatedForm.tsx:28 msgid "Password updated!" -msgstr "Mot de passe mis à jour !" +msgstr "Mot de passe mis à jour !" #: src/Navigation.tsx:162 msgid "People followed by @{0}" @@ -2874,11 +2704,7 @@ msgstr "Permission d’accès à la pellicule refusée. Veuillez l’activer dan #: src/screens/Onboarding/index.tsx:31 msgid "Pets" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:183 -#~ msgid "Phone number" -#~ msgstr "" +msgstr "Animaux domestiques" #: src/view/com/modals/SelfLabel.tsx:121 msgid "Pictures meant for adults." @@ -2916,7 +2742,7 @@ msgstr "Veuillez choisir votre mot de passe." #: src/view/com/auth/create/state.ts:131 msgid "Please complete the verification captcha." -msgstr "" +msgstr "Veuillez compléter le captcha de vérification." #: src/view/com/modals/ChangeEmail.tsx:67 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." @@ -2926,25 +2752,13 @@ msgstr "Veuillez confirmer votre e-mail avant de le modifier. Ceci est temporair msgid "Please enter a name for your app password. All spaces is not allowed." msgstr "Veuillez entrer un nom pour votre mot de passe d’application. Les espaces ne sont pas autorisés." -#: src/view/com/auth/create/Step2.tsx:206 -#~ msgid "Please enter a phone number that can receive SMS text messages." -#~ msgstr "" - #: src/view/com/modals/AddAppPasswords.tsx:145 msgid "Please enter a unique name for this App Password or use our randomly generated one." msgstr "Veuillez saisir un nom unique pour le mot de passe de l’application ou utiliser celui que nous avons généré de manière aléatoire." #: src/components/dialogs/MutedWords.tsx:68 msgid "Please enter a valid word, tag, or phrase to mute" -msgstr "" - -#: src/view/com/auth/create/state.ts:170 -#~ msgid "Please enter the code you received by SMS." -#~ msgstr "" - -#: src/view/com/auth/create/Step2.tsx:282 -#~ msgid "Please enter the verification code sent to {phoneNumberFormatted}." -#~ msgstr "" +msgstr "Veuillez entrer un mot, un mot-clé ou une phrase valide à masquer" #: src/view/com/auth/create/state.ts:103 msgid "Please enter your email." @@ -2952,12 +2766,12 @@ msgstr "Veuillez entrer votre e-mail." #: src/view/com/modals/DeleteAccount.tsx:191 msgid "Please enter your password as well:" -msgstr "Veuillez également entrer votre mot de passe :" +msgstr "Veuillez également entrer votre mot de passe :" #: src/view/com/modals/AppealLabel.tsx:72 #: src/view/com/modals/AppealLabel.tsx:75 msgid "Please tell us why you think this content warning was incorrectly applied!" -msgstr "Dites-nous donc pourquoi vous pensez que cet avertissement de contenu a été appliqué à tort !" +msgstr "Dites-nous donc pourquoi vous pensez que cet avertissement de contenu a été appliqué à tort !" #: src/view/com/modals/VerifyEmail.tsx:101 msgid "Please Verify Your Email" @@ -2969,7 +2783,7 @@ msgstr "Veuillez patienter le temps que votre carte de lien soit chargée" #: src/screens/Onboarding/index.tsx:37 msgid "Politics" -msgstr "" +msgstr "Politique" #: src/view/com/modals/SelfLabel.tsx:111 msgid "Porn" @@ -3018,7 +2832,7 @@ msgstr "Post introuvable" #: src/components/TagMenu/index.tsx:253 msgid "posts" -msgstr "" +msgstr "posts" #: src/view/screens/Profile.tsx:180 msgid "Posts" @@ -3026,7 +2840,7 @@ msgstr "Posts" #: src/components/dialogs/MutedWords.tsx:90 msgid "Posts can be muted based on their text, their tags, or both." -msgstr "" +msgstr "Les posts peuvent être masqués en fonction de leur texte, de leurs mots-clés ou des deux." #: src/view/com/posts/FeedErrorMessage.tsx:64 msgid "Posts hidden" @@ -3082,7 +2896,7 @@ msgstr "Protégez votre compte en vérifiant votre e-mail." #: src/screens/Onboarding/StepFinished.tsx:101 msgid "Public" -msgstr "" +msgstr "Public" #: src/view/screens/ModerationModlists.tsx:61 msgid "Public, shareable lists of users to mute or block in bulk." @@ -3141,7 +2955,7 @@ msgstr "Supprimer" #: src/view/com/feeds/FeedSourceCard.tsx:108 msgid "Remove {0} from my feeds?" -msgstr "Supprimer {0} de mes fils d’actu ?" +msgstr "Supprimer {0} de mes fils d’actu ?" #: src/view/com/util/AccountDropdownBtn.tsx:22 msgid "Remove account" @@ -3170,7 +2984,7 @@ msgstr "Supprimer l’aperçu d’image" #: src/components/dialogs/MutedWords.tsx:343 msgid "Remove mute word from your list" -msgstr "" +msgstr "Supprimer le mot masqué de votre liste" #: src/view/com/modals/Repost.tsx:47 msgid "Remove repost" @@ -3178,11 +2992,11 @@ msgstr "Supprimer le repost" #: src/view/com/feeds/FeedSourceCard.tsx:175 msgid "Remove this feed from my feeds?" -msgstr "Supprimer ce fil d’actu ?" +msgstr "Supprimer ce fil d’actu ?" #: src/view/com/posts/FeedErrorMessage.tsx:132 msgid "Remove this feed from your saved feeds?" -msgstr "Supprimer ce fil d’actu de vos fils d’actu enregistrés ?" +msgstr "Supprimer ce fil d’actu de vos fils d’actu enregistrés ?" #: src/view/com/modals/ListAddRemoveUsers.tsx:199 #: src/view/com/modals/UserAddRemoveLists.tsx:152 @@ -3285,14 +3099,10 @@ msgstr "Reposts de ce post" msgid "Request Change" msgstr "Demande de modification" -#: src/view/com/auth/create/Step2.tsx:219 -#~ msgid "Request code" -#~ msgstr "" - #: src/view/com/modals/ChangePassword.tsx:239 #: src/view/com/modals/ChangePassword.tsx:241 msgid "Request Code" -msgstr "" +msgstr "Demander un code" #: src/view/screens/Settings/index.tsx:456 msgid "Require alt text before posting" @@ -3309,7 +3119,7 @@ msgstr "Réinitialiser le code" #: src/view/com/modals/ChangePassword.tsx:190 msgid "Reset Code" -msgstr "" +msgstr "Code de réinitialisation" #: src/view/screens/Settings/index.tsx:824 msgid "Reset onboarding" @@ -3359,18 +3169,10 @@ msgstr "Réessaye la dernière action, qui a échoué" msgid "Retry" msgstr "Réessayer" -#: src/view/com/auth/create/Step2.tsx:247 -#~ msgid "Retry." -#~ msgstr "" - #: src/view/screens/ProfileList.tsx:903 msgid "Return to previous page" msgstr "Retourne à la page précédente" -#: src/view/shell/desktop/RightNav.tsx:55 -#~ msgid "SANDBOX. Posts and accounts are not permanent." -#~ msgstr "SANDBOX. Les posts et les comptes ne sont pas permanents." - #: src/view/com/lightbox/Lightbox.tsx:132 #: src/view/com/modals/CreateOrEditList.tsx:345 msgctxt "action" @@ -3416,7 +3218,7 @@ msgstr "Enregistre le changement de pseudo en {handle}" #: src/screens/Onboarding/index.tsx:36 msgid "Science" -msgstr "" +msgstr "Science" #: src/view/screens/ProfileList.tsx:859 msgid "Scroll to top" @@ -3442,23 +3244,15 @@ msgstr "Recherche" #: src/view/screens/Search/Search.tsx:735 #: src/view/shell/desktop/Search.tsx:255 msgid "Search for \"{query}\"" -msgstr "" +msgstr "Recherche de « {query} »" #: src/components/TagMenu/index.tsx:145 msgid "Search for all posts by @{authorHandle} with tag {displayTag}" -msgstr "" - -#: src/components/TagMenu/index.tsx:145 -#~ msgid "Search for all posts by @{authorHandle} with tag {tag}" -#~ msgstr "" +msgstr "Rechercher tous les posts de @{authorHandle} avec le mot-clé {displayTag}" #: src/components/TagMenu/index.tsx:94 msgid "Search for all posts with tag {displayTag}" -msgstr "" - -#: src/components/TagMenu/index.tsx:90 -#~ msgid "Search for all posts with tag {tag}" -#~ msgstr "" +msgstr "Rechercher tous les posts avec le mot-clé {displayTag}" #: src/view/com/auth/LoggedOut.tsx:104 #: src/view/com/auth/LoggedOut.tsx:105 @@ -3472,27 +3266,19 @@ msgstr "Étape de sécurité requise" #: src/components/TagMenu/index.web.tsx:66 msgid "See {truncatedTag} posts" -msgstr "" +msgstr "Voir les posts {truncatedTag}" #: src/components/TagMenu/index.web.tsx:83 msgid "See {truncatedTag} posts by user" -msgstr "" +msgstr "Voir les posts {truncatedTag} de ce compte" #: src/components/TagMenu/index.tsx:128 msgid "See <0>{displayTag}</0> posts" -msgstr "" +msgstr "Voir les posts <0>{displayTag}</0>" #: src/components/TagMenu/index.tsx:187 msgid "See <0>{displayTag}</0> posts by this user" -msgstr "" - -#: src/components/TagMenu/index.tsx:128 -#~ msgid "See <0>{tag}</0> posts" -#~ msgstr "" - -#: src/components/TagMenu/index.tsx:189 -#~ msgid "See <0>{tag}</0> posts by this user" -#~ msgstr "" +msgstr "Voir les posts <0>{displayTag}</0> de ce compte" #: src/view/screens/SavedFeeds.tsx:163 msgid "See this guide" @@ -3506,10 +3292,6 @@ msgstr "Voir la suite" msgid "Select {item}" msgstr "Sélectionner {item}" -#: src/view/com/modals/ServerInput.tsx:75 -#~ msgid "Select Bluesky Social" -#~ msgstr "Sélectionner Bluesky Social" - #: src/view/com/auth/login/Login.tsx:117 msgid "Select from an existing account" msgstr "Sélectionner un compte existant" @@ -3525,23 +3307,19 @@ msgstr "Sélectionner un service" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:52 msgid "Select some accounts below to follow" -msgstr "" +msgstr "Sélectionnez quelques comptes à suivre ci-dessous" #: src/view/com/auth/server-input/index.tsx:82 msgid "Select the service that hosts your data." -msgstr "" - -#: src/screens/Onboarding/StepModeration/index.tsx:49 -#~ msgid "Select the types of content that you want to see (or not see), and we'll handle the rest." -#~ msgstr "" +msgstr "Sélectionnez le service qui héberge vos données." #: src/screens/Onboarding/StepTopicalFeeds.tsx:96 msgid "Select topical feeds to follow from the list below" -msgstr "" +msgstr "Sélectionnez les fils d’actu thématiques à suivre dans la liste ci-dessous" #: src/screens/Onboarding/StepModeration/index.tsx:75 msgid "Select what you want to see (or not see), and we’ll handle the rest." -msgstr "" +msgstr "Sélectionnez ce que vous voulez voir (ou ne pas voir), et nous nous occupons du reste." #: src/view/screens/LanguageSettings.tsx:281 msgid "Select which languages you want your subscribed feeds to include. If none are selected, all languages will be shown." @@ -3553,11 +3331,7 @@ msgstr "Sélectionnez la langue de votre application à afficher par défaut" #: src/screens/Onboarding/StepInterests/index.tsx:196 msgid "Select your interests from the options below" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:155 -#~ msgid "Select your phone's country" -#~ msgstr "" +msgstr "Sélectionnez vos centres d’intérêt parmi les options ci-dessous" #: src/view/screens/LanguageSettings.tsx:190 msgid "Select your preferred language for translations in your feed." @@ -3565,11 +3339,11 @@ msgstr "Sélectionnez votre langue préférée pour traduire votre fils d’actu #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:116 msgid "Select your primary algorithmic feeds" -msgstr "" +msgstr "Sélectionnez vos principaux fils d’actu algorithmiques" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:142 msgid "Select your secondary algorithmic feeds" -msgstr "" +msgstr "Sélectionnez vos fils d’actu algorithmiques secondaires" #: src/view/com/modals/VerifyEmail.tsx:202 #: src/view/com/modals/VerifyEmail.tsx:204 @@ -3600,7 +3374,7 @@ msgstr "Envoie un e-mail avec le code de confirmation pour la suppression du com #: src/view/com/auth/server-input/index.tsx:110 msgid "Server address" -msgstr "" +msgstr "Adresse du serveur" #: src/view/com/modals/ContentFilteringSettings.tsx:311 msgid "Set {value} for {labelGroup} content moderation policy" @@ -3626,11 +3400,11 @@ msgstr "Change le thème de couleur en fonction du paramètre système" #: src/view/screens/Settings/index.tsx:514 msgid "Set dark theme to the dark theme" -msgstr "" +msgstr "Choisir le thème le plus sombre comme thème sombre" #: src/view/screens/Settings/index.tsx:507 msgid "Set dark theme to the dim theme" -msgstr "" +msgstr "Choisir le thème atténué comme thème sombre" #: src/view/com/auth/login/SetNewPasswordForm.tsx:104 msgid "Set new password" @@ -3642,31 +3416,27 @@ msgstr "Définit le mot de passe" #: src/view/screens/PreferencesFollowingFeed.tsx:225 msgid "Set this setting to \"No\" to hide all quote posts from your feed. Reposts will still be visible." -msgstr "Choisissez « Non » pour cacher toutes les citations sur votre fils d’actu. Les reposts seront toujours visibles." +msgstr "Choisissez « Non » pour cacher toutes les citations sur votre fils d’actu. Les reposts seront toujours visibles." #: src/view/screens/PreferencesFollowingFeed.tsx:122 msgid "Set this setting to \"No\" to hide all replies from your feed." -msgstr "Choisissez « Non » pour cacher toutes les réponses dans votre fils d’actu." +msgstr "Choisissez « Non » pour cacher toutes les réponses dans votre fils d’actu." #: src/view/screens/PreferencesFollowingFeed.tsx:191 msgid "Set this setting to \"No\" to hide all reposts from your feed." -msgstr "Choisissez « Non » pour cacher toutes les reposts de votre fils d’actu." +msgstr "Choisissez « Non » pour cacher toutes les reposts de votre fils d’actu." #: 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 "Choisissez « Oui » pour afficher les réponses dans un fil de discussion. C’est une fonctionnalité expérimentale." - -#: src/view/screens/PreferencesHomeFeed.tsx:261 -#~ msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your following feed. This is an experimental feature." -#~ msgstr "Choisissez « Oui » pour afficher des échantillons de vos fils d’actu enregistrés dans votre fils d’actu suivant. C’est une fonctionnalité expérimentale." +msgstr "Choisissez « Oui » pour afficher les réponses dans un fil de discussion. C’est une fonctionnalité expérimentale." #: src/view/screens/PreferencesFollowingFeed.tsx:261 msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your Following feed. This is an experimental feature." -msgstr "" +msgstr "Choisissez « Oui » pour afficher des échantillons de vos fils d’actu enregistrés dans votre fil d’actu « Following ». C’est une fonctionnalité expérimentale." #: src/screens/Onboarding/Layout.tsx:50 msgid "Set up your account" -msgstr "" +msgstr "Créez votre compte" #: src/view/com/modals/ChangeHandle.tsx:266 msgid "Sets Bluesky username" @@ -3754,15 +3524,15 @@ msgstr "Afficher les citations" #: src/screens/Onboarding/StepFollowingFeed.tsx:118 msgid "Show quote-posts in Following feed" -msgstr "" +msgstr "Afficher les citations dans le fil d’actu « Following »" #: src/screens/Onboarding/StepFollowingFeed.tsx:134 msgid "Show quotes in Following" -msgstr "" +msgstr "Afficher les citations dans le fil d’actu « Following »" #: src/screens/Onboarding/StepFollowingFeed.tsx:94 msgid "Show re-posts in Following feed" -msgstr "" +msgstr "Afficher les reposts dans le fil d’actu « Following »" #: src/view/screens/PreferencesFollowingFeed.tsx:119 msgid "Show Replies" @@ -3774,11 +3544,11 @@ msgstr "Afficher les réponses des personnes que vous suivez avant toutes les au #: src/screens/Onboarding/StepFollowingFeed.tsx:86 msgid "Show replies in Following" -msgstr "" +msgstr "Afficher les réponses dans le fil d’actu « Following »" #: src/screens/Onboarding/StepFollowingFeed.tsx:70 msgid "Show replies in Following feed" -msgstr "" +msgstr "Afficher les réponses dans le fil d’actu « Following »" #: src/view/screens/PreferencesFollowingFeed.tsx:70 msgid "Show replies with at least {value} {0}" @@ -3790,7 +3560,7 @@ msgstr "Afficher les reposts" #: src/screens/Onboarding/StepFollowingFeed.tsx:110 msgid "Show reposts in Following" -msgstr "" +msgstr "Afficher les reposts dans le fil d’actu « Following »" #: src/view/com/util/moderation/ContentHider.tsx:67 #: src/view/com/util/moderation/PostHider.tsx:61 @@ -3890,31 +3660,19 @@ msgstr "Ignorer" #: src/screens/Onboarding/StepInterests/index.tsx:232 msgid "Skip this flow" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:82 -#~ msgid "SMS verification" -#~ msgstr "" +msgstr "Passer cette étape" #: src/screens/Onboarding/index.tsx:40 msgid "Software Dev" -msgstr "" - -#: src/view/com/modals/ProfilePreview.tsx:62 -#~ msgid "Something went wrong and we're not sure what." -#~ msgstr "Quelque chose n’a pas marché, mais on ne sait pas trop quoi." +msgstr "Développement de logiciels" #: src/components/Lists.tsx:203 msgid "Something went wrong!" -msgstr "" - -#: src/view/com/modals/Waitlist.tsx:51 -#~ msgid "Something went wrong. Check your email and try again." -#~ msgstr "Quelque chose n’a pas marché. Vérifiez vos e-mails et réessayez." +msgstr "Quelque chose n’a pas marché !" #: src/App.native.tsx:66 msgid "Sorry! Your session expired. Please log in again." -msgstr "Désolé ! Votre session a expiré. Essayez de vous reconnecter." +msgstr "Désolé ! Votre session a expiré. Essayez de vous reconnecter." #: src/view/screens/PreferencesThreads.tsx:69 msgid "Sort Replies" @@ -3922,20 +3680,16 @@ msgstr "Trier les réponses" #: src/view/screens/PreferencesThreads.tsx:72 msgid "Sort replies to the same post by:" -msgstr "Trier les réponses au même post par :" +msgstr "Trier les réponses au même post par :" #: src/screens/Onboarding/index.tsx:30 msgid "Sports" -msgstr "" +msgstr "Sports" #: src/view/com/modals/crop-image/CropImage.web.tsx:122 msgid "Square" msgstr "Carré" -#: src/view/com/modals/ServerInput.tsx:62 -#~ msgid "Staging" -#~ msgstr "Serveur de test" - #: src/view/screens/Settings/index.tsx:871 msgid "Status page" msgstr "État du service" @@ -3964,7 +3718,7 @@ msgstr "S’abonner" #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:173 #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:308 msgid "Subscribe to the {0} feed" -msgstr "" +msgstr "S’abonner au fil d’actu {0}" #: src/view/screens/ProfileList.tsx:604 msgid "Subscribe to this list" @@ -3988,10 +3742,6 @@ msgstr "Suggestif" msgid "Support" msgstr "Soutien" -#: src/view/com/modals/ProfilePreview.tsx:110 -#~ msgid "Swipe up to see more" -#~ msgstr "Glisser vers le haut pour en voir plus" - #: src/view/com/modals/SwitchAccount.tsx:117 msgid "Switch Account" msgstr "Changer de compte" @@ -4016,15 +3766,11 @@ msgstr "Journal système" #: src/components/dialogs/MutedWords.tsx:337 msgid "tag" -msgstr "" +msgstr "mot-clé" #: src/components/TagMenu/index.tsx:78 msgid "Tag menu: {displayTag}" -msgstr "" - -#: src/components/TagMenu/index.tsx:74 -#~ msgid "Tag menu: {tag}" -#~ msgstr "" +msgstr "Menu de mot-clé : {displayTag}" #: src/view/com/modals/crop-image/CropImage.web.tsx:112 msgid "Tall" @@ -4036,7 +3782,7 @@ msgstr "Tapper pour voir en entier" #: src/screens/Onboarding/index.tsx:39 msgid "Tech" -msgstr "" +msgstr "Technologie" #: src/view/shell/desktop/RightNav.tsx:81 msgid "Terms" @@ -4051,7 +3797,7 @@ msgstr "Conditions d’utilisation" #: src/components/dialogs/MutedWords.tsx:337 msgid "text" -msgstr "" +msgstr "texte" #: src/view/com/modals/AppealLabel.tsx:70 #: src/view/com/modals/report/InputIssueDetails.tsx:51 @@ -4060,7 +3806,7 @@ msgstr "Champ de saisie de texte" #: src/view/com/auth/create/CreateAccount.tsx:94 msgid "That handle is already taken." -msgstr "" +msgstr "Ce pseudo est déjà occupé." #: src/view/com/profile/ProfileHeader.tsx:263 msgid "The account will be able to interact with you after unblocking." @@ -4076,7 +3822,7 @@ msgstr "Notre politique de droits d’auteur a été déplacée vers <0/>" #: src/screens/Onboarding/Layout.tsx:60 msgid "The following steps will help customize your Bluesky experience." -msgstr "" +msgstr "Les étapes suivantes vous aideront à personnaliser votre expérience avec Bluesky." #: src/view/com/post-thread/PostThread.tsx:517 msgid "The post may have been deleted." @@ -4096,7 +3842,7 @@ msgstr "Nos conditions d’utilisation ont été déplacées vers" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:150 msgid "There are many feeds to try:" -msgstr "" +msgstr "Il existe de nombreux fils d’actu à essayer :" #: src/view/screens/ProfileFeed.tsx:550 msgid "There was an an issue contacting the server, please check your internet connection and try again." @@ -4161,7 +3907,7 @@ msgstr "Il y a eu un problème lors de la récupération de vos mots de passe dâ #: src/view/com/profile/ProfileHeader.tsx:250 #: src/view/com/profile/ProfileHeader.tsx:272 msgid "There was an issue! {0}" -msgstr "Il y a eu un problème ! {0}" +msgstr "Il y a eu un problème ! {0}" #: src/view/screens/ProfileList.tsx:288 #: src/view/screens/ProfileList.tsx:307 @@ -4172,23 +3918,19 @@ msgstr "Il y a eu un problème. Veuillez vérifier votre connexion Internet et r #: src/view/com/util/ErrorBoundary.tsx:36 msgid "There was an unexpected issue in the application. Please let us know if this happened to you!" -msgstr "Un problème inattendu s’est produit dans l’application. N’hésitez pas à nous faire savoir si cela vous est arrivé !" +msgstr "Un problème inattendu s’est produit dans l’application. N’hésitez pas à nous faire savoir si cela vous est arrivé !" #: src/screens/Deactivated.tsx:106 msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:55 -#~ msgid "There's something wrong with this number. Please choose your country and enter your full phone number!" -#~ msgstr "" +msgstr "Il y a eu un afflux de nouveaux personnes sur Bluesky ! Nous activerons ton compte dès que possible." #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:138 msgid "These are popular accounts you might like:" -msgstr "" +msgstr "Voici des comptes populaires qui pourraient vous intéresser :" #: src/view/com/util/moderation/ScreenHider.tsx:88 msgid "This {screenDescription} has been flagged:" -msgstr "Ce {screenDescription} a été signalé :" +msgstr "Ce {screenDescription} a été signalé :" #: src/view/com/util/moderation/ScreenHider.tsx:83 msgid "This account has requested that users sign in to view their profile." @@ -4196,7 +3938,7 @@ msgstr "Ce compte a demandé aux personnes de se connecter pour voir son profil. #: src/view/com/modals/EmbedConsent.tsx:68 msgid "This content is hosted by {0}. Do you want to enable external media?" -msgstr "Ce contenu est hébergé par {0}. Voulez-vous activer les médias externes ?" +msgstr "Ce contenu est hébergé par {0}. Voulez-vous activer les médias externes ?" #: src/view/com/modals/ModerationDetails.tsx:67 msgid "This content is not available because one of the users involved has blocked the other." @@ -4208,7 +3950,7 @@ msgstr "Ce contenu n’est pas visible sans un compte Bluesky." #: src/view/screens/Settings/ExportCarDialog.tsx:75 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost.</0>" -msgstr "" +msgstr "Cette fonctionnalité est en version bêta. Vous pouvez en savoir plus sur les exportations de dépôts dans <0>ce blogpost.</0>" #: src/view/com/posts/FeedErrorMessage.tsx:114 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." @@ -4218,11 +3960,11 @@ msgstr "Ce fil d’actu reçoit actuellement un trafic important, il est tempora #: src/view/screens/ProfileFeed.tsx:476 #: src/view/screens/ProfileList.tsx:661 msgid "This feed is empty!" -msgstr "Ce fil d’actu est vide !" +msgstr "Ce fil d’actu est vide !" #: 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 "Ce fil d’actu est vide ! Vous devriez peut-être suivre plus de comptes ou ajuster vos paramètres de langue." +msgstr "Ce fil d’actu est vide ! Vous devriez peut-être suivre plus de comptes ou ajuster vos paramètres de langue." #: src/view/com/modals/BirthDateSettings.tsx:61 msgid "This information is not shared with other users." @@ -4234,11 +3976,11 @@ msgstr "Ceci est important au cas où vous auriez besoin de changer d’e-mail o #: src/view/com/modals/LinkWarning.tsx:58 msgid "This link is taking you to the following website:" -msgstr "Ce lien vous conduit au site Web suivant :" +msgstr "Ce lien vous conduit au site Web suivant :" #: src/view/screens/ProfileList.tsx:839 msgid "This list is empty!" -msgstr "Cette liste est vide !" +msgstr "Cette liste est vide !" #: src/view/com/modals/AddAppPasswords.tsx:106 msgid "This name is already in use" @@ -4258,19 +4000,15 @@ msgstr "Ce compte est inclus dans la liste <0/> que vous avez bloquée." #: src/view/com/modals/ModerationDetails.tsx:74 msgid "This user is included in the <0/> list which you have muted." -msgstr "" - -#: src/view/com/modals/ModerationDetails.tsx:74 -#~ msgid "This user is included the <0/> list which you have muted." -#~ msgstr "Ce compte est inclus dans la liste <0/> que vous avez masquée." +msgstr "Ce compte est inclus dans la liste <0/> que vous avez masquée." #: src/view/com/modals/SelfLabel.tsx:137 msgid "This warning is only available for posts with media attached." -msgstr "Cet avertissement n’est disponible que pour les messages contenant des médias." +msgstr "Cet avertissement n’est disponible que pour les posts contenant des médias." #: src/components/dialogs/MutedWords.tsx:285 msgid "This will delete {0} from your muted words. You can always add it back later." -msgstr "" +msgstr "Cela supprimera {0} de vos mots masqués. Vous pourrez toujours le réintégrer plus tard." #: src/view/com/util/forms/PostDropdownBtn.tsx:282 msgid "This will hide this post from your feeds." @@ -4291,7 +4029,7 @@ msgstr "Préférences de fils de discussion" #: src/components/dialogs/MutedWords.tsx:113 msgid "Toggle between muted word options." -msgstr "" +msgstr "Basculer entre les options pour les mots masqués." #: src/view/com/util/forms/DropdownButton.tsx:246 msgid "Toggle dropdown" @@ -4375,7 +4113,7 @@ msgstr "Réafficher" #: src/components/TagMenu/index.web.tsx:104 msgid "Unmute {truncatedTag}" -msgstr "" +msgstr "Réafficher {truncatedTag}" #: src/view/com/profile/ProfileHeader.tsx:326 msgid "Unmute Account" @@ -4383,11 +4121,7 @@ msgstr "Réafficher ce compte" #: src/components/TagMenu/index.tsx:208 msgid "Unmute all {displayTag} posts" -msgstr "" - -#: src/components/TagMenu/index.tsx:210 -#~ msgid "Unmute all {tag} posts" -#~ msgstr "" +msgstr "Réafficher tous les posts {displayTag}" #: src/view/com/util/forms/PostDropdownBtn.tsx:251 #: src/view/com/util/forms/PostDropdownBtn.tsx:256 @@ -4421,7 +4155,7 @@ msgstr "Mise à jour…" #: src/view/com/modals/ChangeHandle.tsx:455 msgid "Upload a text file to:" -msgstr "Envoyer un fichier texte vers :" +msgstr "Envoyer un fichier texte vers :" #: src/view/screens/AppPasswords.tsx:195 msgid "Use app passwords to login to other Bluesky clients without giving full access to your account or password." @@ -4434,24 +4168,20 @@ msgstr "Utiliser le fournisseur par défaut" #: src/view/com/modals/InAppBrowserConsent.tsx:56 #: src/view/com/modals/InAppBrowserConsent.tsx:58 msgid "Use in-app browser" -msgstr "" +msgstr "Utiliser le navigateur interne à l’appli" #: src/view/com/modals/InAppBrowserConsent.tsx:66 #: src/view/com/modals/InAppBrowserConsent.tsx:68 msgid "Use my default browser" -msgstr "" +msgstr "Utiliser mon navigateur par défaut" #: src/view/com/modals/AddAppPasswords.tsx:155 msgid "Use this to sign into the other app along with your handle." msgstr "Utilisez-le pour vous connecter à l’autre application avec votre identifiant." -#: src/view/com/modals/ServerInput.tsx:105 -#~ msgid "Use your domain as your Bluesky client service provider" -#~ msgstr "Utilise votre domaine comme votre fournisseur de client Bluesky" - #: src/view/com/modals/InviteCodes.tsx:200 msgid "Used by:" -msgstr "Utilisé par :" +msgstr "Utilisé par :" #: src/view/com/modals/ModerationDetails.tsx:54 msgid "User Blocked" @@ -4511,11 +4241,7 @@ msgstr "comptes suivis par <0/>" #: src/view/com/modals/Threadgate.tsx:106 msgid "Users in \"{0}\"" -msgstr "Comptes dans « {0} »" - -#: src/view/com/auth/create/Step2.tsx:243 -#~ msgid "Verification code" -#~ msgstr "" +msgstr "Comptes dans « {0} »" #: src/view/screens/Settings/index.tsx:910 msgid "Verify email" @@ -4540,7 +4266,7 @@ msgstr "Vérifiez votre e-mail" #: src/screens/Onboarding/index.tsx:42 msgid "Video Games" -msgstr "" +msgstr "Jeux vidéo" #: src/view/com/profile/ProfileHeader.tsx:662 msgid "View {0}'s avatar" @@ -4573,43 +4299,39 @@ msgstr "Avertir" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:134 msgid "We also think you'll like \"For You\" by Skygaze:" -msgstr "" +msgstr "Nous pensons également que vous aimerez « For You » de Skygaze :" #: src/screens/Hashtag.tsx:132 msgid "We couldn't find any results for that hashtag." -msgstr "" +msgstr "Nous n’avons trouvé aucun résultat pour ce mot-clé." #: src/screens/Deactivated.tsx:133 msgid "We estimate {estimatedTime} until your account is ready." -msgstr "" +msgstr "Nous estimons que votre compte sera prêt dans {estimatedTime}." #: src/screens/Onboarding/StepFinished.tsx:93 msgid "We hope you have a wonderful time. Remember, Bluesky is:" -msgstr "" +msgstr "Nous espérons que vous passerez un excellent moment. N’oubliez pas que Bluesky est :" #: src/view/com/posts/DiscoverFallbackHeader.tsx:29 msgid "We ran out of posts from your follows. Here's the latest from <0/>." -msgstr "" - -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:118 -#~ msgid "We recommend \"For You\" by Skygaze:" -#~ msgstr "" +msgstr "Nous n’avons plus de posts provenant des comptes que vous suivez. Voici le dernier de <0/>." #: src/components/dialogs/MutedWords.tsx:204 msgid "We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." -msgstr "" +msgstr "Nous vous recommandons d’éviter les mots communs qui apparaissent dans de nombreux posts, car cela peut avoir pour conséquence qu’aucun post ne s’affiche." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124 msgid "We recommend our \"Discover\" feed:" -msgstr "" +msgstr "Nous vous recommandons notre fil d’actu « Discover » :" #: src/screens/Onboarding/StepInterests/index.tsx:133 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 "" +msgstr "Nous n’avons pas pu nous connecter. Veuillez réessayer pour continuer à configurer votre compte. Si l’échec persiste, vous pouvez sauter cette étape." #: src/screens/Deactivated.tsx:137 msgid "We will let you know when your account is ready." -msgstr "" +msgstr "Nous vous informerons lorsque votre compte sera prêt." #: src/view/com/modals/AppealLabel.tsx:48 msgid "We'll look into your appeal promptly." @@ -4617,11 +4339,11 @@ msgstr "Nous examinerons votre appel rapidement." #: src/screens/Onboarding/StepInterests/index.tsx:138 msgid "We'll use this to help customize your experience." -msgstr "" +msgstr "Nous utiliserons ces informations pour personnaliser votre expérience." #: src/view/com/auth/create/CreateAccount.tsx:134 msgid "We're so excited to have you join us!" -msgstr "Nous sommes ravis de vous accueillir !" +msgstr "Nous sommes ravis de vous accueillir !" #: src/view/screens/ProfileList.tsx:86 msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." @@ -4629,7 +4351,7 @@ msgstr "Nous sommes désolés, mais nous n’avons pas pu charger cette liste. S #: src/components/dialogs/MutedWords.tsx:230 msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." -msgstr "" +msgstr "Nous sommes désolés, mais nous n’avons pas pu charger vos mots masqués pour le moment. Veuillez réessayer." #: src/view/screens/Search/Search.tsx:254 msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." @@ -4638,7 +4360,7 @@ msgstr "Nous sommes désolés, mais votre recherche a été annulée. Veuillez r #: src/components/Lists.tsx:211 #: src/view/screens/NotFound.tsx:48 msgid "We're sorry! We can't find the page you were looking for." -msgstr "Nous sommes désolés ! La page que vous recherchez est introuvable." +msgstr "Nous sommes désolés ! La page que vous recherchez est introuvable." #: src/view/com/auth/onboarding/WelcomeMobile.tsx:46 msgid "Welcome to <0>Bluesky</0>" @@ -4646,29 +4368,29 @@ msgstr "Bienvenue sur <0>Bluesky</0>" #: src/screens/Onboarding/StepInterests/index.tsx:130 msgid "What are your interests?" -msgstr "" +msgstr "Quels sont vos centres d’intérêt ?" #: src/view/com/modals/report/Modal.tsx:169 msgid "What is the issue with this {collectionName}?" -msgstr "Quel est le problème avec cette {collectionName} ?" +msgstr "Quel est le problème avec cette {collectionName} ?" #: src/view/com/auth/SplashScreen.tsx:59 #: src/view/com/composer/Composer.tsx:286 msgid "What's up?" -msgstr "Quoi de neuf ?" +msgstr "Quoi de neuf ?" #: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:78 msgid "Which languages are used in this post?" -msgstr "Quelles sont les langues utilisées dans ce post ?" +msgstr "Quelles sont les langues utilisées dans ce post ?" #: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:77 msgid "Which languages would you like to see in your algorithmic feeds?" -msgstr "Quelles langues aimeriez-vous voir apparaître dans vos flux algorithmiques ?" +msgstr "Quelles langues aimeriez-vous voir apparaître dans vos fils d’actu algorithmiques ?" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:47 #: src/view/com/modals/Threadgate.tsx:66 msgid "Who can reply" -msgstr "Qui peut répondre ?" +msgstr "Qui peut répondre ?" #: src/view/com/modals/crop-image/CropImage.web.tsx:102 msgid "Wide" @@ -4685,11 +4407,7 @@ msgstr "Rédigez votre réponse" #: src/screens/Onboarding/index.tsx:28 msgid "Writers" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:263 -#~ msgid "XXXXXX" -#~ msgstr "" +msgstr "Écrivain·e·s" #: src/view/com/composer/select-language/SuggestedLanguage.tsx:77 #: src/view/screens/PreferencesFollowingFeed.tsx:129 @@ -4701,26 +4419,18 @@ msgstr "" msgid "Yes" msgstr "Oui" -#: src/screens/Onboarding/StepModeration/index.tsx:46 -#~ msgid "You are in control" -#~ msgstr "" - #: src/screens/Deactivated.tsx:130 msgid "You are in line." -msgstr "" +msgstr "Vous êtes dans la file d’attente." #: src/view/com/posts/FollowingEmptyState.tsx:67 #: src/view/com/posts/FollowingEndOfFeed.tsx:68 msgid "You can also discover new Custom Feeds to follow." msgstr "Vous pouvez aussi découvrir de nouveaux fils d’actu personnalisés à suivre." -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:123 -#~ msgid "You can also try our \"Discover\" algorithm:" -#~ msgstr "" - #: src/screens/Onboarding/StepFollowingFeed.tsx:142 msgid "You can change these settings later." -msgstr "" +msgstr "Vous pouvez modifier ces paramètres ultérieurement." #: src/view/com/auth/login/Login.tsx:158 #: src/view/com/auth/login/PasswordUpdatedForm.tsx:31 @@ -4729,7 +4439,7 @@ msgstr "Vous pouvez maintenant vous connecter avec votre nouveau mot de passe." #: src/view/com/modals/InviteCodes.tsx:66 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 "Vous n’avez encore aucun code d’invitation ! Nous vous en enverrons lorsque vous serez sur Bluesky depuis un peu plus longtemps." +msgstr "Vous n’avez encore aucun code d’invitation ! Nous vous en enverrons lorsque vous serez sur Bluesky depuis un peu plus longtemps." #: src/view/screens/SavedFeeds.tsx:102 msgid "You don't have any pinned feeds." @@ -4737,7 +4447,7 @@ msgstr "Vous n’avez encore aucun fil épinglé." #: src/view/screens/Feeds.tsx:452 msgid "You don't have any saved feeds!" -msgstr "Vous n’avez encore aucun fil enregistré !" +msgstr "Vous n’avez encore aucun fil enregistré !" #: src/view/screens/SavedFeeds.tsx:135 msgid "You don't have any saved feeds." @@ -4756,7 +4466,7 @@ msgstr "Vous avez bloqué ce compte. Vous ne pouvez pas voir son contenu." #: src/view/com/modals/ChangePassword.tsx:87 #: src/view/com/modals/ChangePassword.tsx:121 msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." -msgstr "" +msgstr "Vous avez introduit un code non valide. Il devrait ressembler à XXXXX-XXXXX." #: src/view/com/modals/ModerationDetails.tsx:87 msgid "You have muted this user." @@ -4773,7 +4483,7 @@ msgstr "Vous n’avez aucune liste." #: src/view/screens/ModerationBlockedAccounts.tsx:132 msgid "You have not blocked any accounts yet. To block an account, go to their profile and selected \"Block account\" from the menu on their account." -msgstr "Vous n’avez pas encore bloqué de comptes. Pour bloquer un compte, accédez à son profil et sélectionnez « Bloquer le compte » dans le menu de son compte." +msgstr "Vous n’avez pas encore bloqué de comptes. Pour bloquer un compte, accédez à son profil et sélectionnez « Bloquer le compte » dans le menu de son compte." #: src/view/screens/AppPasswords.tsx:87 msgid "You have not created any app passwords yet. You can create one by pressing the button below." @@ -4781,11 +4491,11 @@ msgstr "Vous n’avez encore créé aucun mot de passe pour l’appli. Vous pouv #: src/view/screens/ModerationMutedAccounts.tsx:131 msgid "You have not muted any accounts yet. To mute an account, go to their profile and selected \"Mute account\" from the menu on their account." -msgstr "Vous n’avez encore masqué aucun compte. Pour désactiver un compte, allez sur son profil et sélectionnez « Masquer le compte » dans le menu de son compte." +msgstr "Vous n’avez encore masqué aucun compte. Pour désactiver un compte, allez sur son profil et sélectionnez « Masquer le compte » dans le menu de son compte." #: src/components/dialogs/MutedWords.tsx:250 msgid "You haven't muted any words or tags yet" -msgstr "" +msgstr "Vous n’avez pas encore masqué de mot ou de mot-clé" #: src/view/com/modals/ContentFilteringSettings.tsx:175 msgid "You must be 18 or older to enable adult content." @@ -4793,7 +4503,7 @@ msgstr "Vous devez avoir 18 ans ou plus pour activer le contenu pour adultes." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:103 msgid "You must be 18 years or older to enable adult content" -msgstr "" +msgstr "Vous devez avoir 18 ans ou plus pour activer le contenu pour adultes." #: src/view/com/util/forms/PostDropdownBtn.tsx:147 msgid "You will no longer receive notifications for this thread" @@ -4805,25 +4515,25 @@ msgstr "Vous recevrez désormais des notifications pour ce fil de discussion" #: src/view/com/auth/login/SetNewPasswordForm.tsx:107 msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password." -msgstr "Vous recevrez un e-mail contenant un « code de réinitialisation » Saisissez ce code ici, puis votre nouveau mot de passe." +msgstr "Vous recevrez un e-mail contenant un « code de réinitialisation ». Saisissez ce code ici, puis votre nouveau mot de passe." #: src/screens/Onboarding/StepModeration/index.tsx:72 msgid "You're in control" -msgstr "" +msgstr "Vous avez le contrôle" #: src/screens/Deactivated.tsx:87 #: src/screens/Deactivated.tsx:88 #: src/screens/Deactivated.tsx:103 msgid "You're in line" -msgstr "" +msgstr "Vous êtes dans la file d’attente" #: src/screens/Onboarding/StepFinished.tsx:90 msgid "You're ready to go!" -msgstr "" +msgstr "Vous êtes prêt à partir !" #: src/view/com/posts/FollowingEndOfFeed.tsx:48 msgid "You've reached the end of your feed! Find some more accounts to follow." -msgstr "Vous avez atteint la fin de votre fil d’actu ! Trouvez d’autres comptes à suivre." +msgstr "Vous avez atteint la fin de votre fil d’actu ! Trouvez d’autres comptes à suivre." #: src/view/com/auth/create/Step1.tsx:67 msgid "Your account" @@ -4835,7 +4545,7 @@ msgstr "Votre compte a été supprimé" #: src/view/screens/Settings/ExportCarDialog.tsx:47 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 "" +msgstr "Le dépôt de votre compte, qui contient toutes les données publiques, peut être téléchargé sous la forme d’un fichier « CAR ». Ce fichier n’inclut pas les éléments multimédias, tels que les images, ni vos données privées, qui doivent être récupérées séparément." #: src/view/com/auth/create/Step1.tsx:215 msgid "Your birth date" @@ -4843,11 +4553,11 @@ msgstr "Votre date de naissance" #: src/view/com/modals/InAppBrowserConsent.tsx:47 msgid "Your choice will be saved, but can be changed later in settings." -msgstr "" +msgstr "Votre choix sera enregistré, mais vous pourrez le modifier ultérieurement dans les paramètres." #: src/screens/Onboarding/StepFollowingFeed.tsx:61 msgid "Your default feed is \"Following\"" -msgstr "" +msgstr "Votre fil d’actu par défaut est « Following »" #: src/view/com/auth/create/state.ts:110 #: src/view/com/auth/login/ForgotPasswordForm.tsx:70 @@ -4855,10 +4565,6 @@ msgstr "" msgid "Your email appears to be invalid." msgstr "Votre e-mail semble être invalide." -#: src/view/com/modals/Waitlist.tsx:109 -#~ msgid "Your email has been saved! We'll be in touch soon." -#~ msgstr "Votre e-mail a été enregistré ! Nous vous contacterons bientôt." - #: src/view/com/modals/ChangeEmail.tsx:125 msgid "Your email has been updated but not verified. As a next step, please verify your new email." msgstr "Votre e-mail a été mis à jour, mais n’a pas été vérifié. L’étape suivante consiste à vérifier votre nouvel e-mail." @@ -4869,7 +4575,7 @@ msgstr "Votre e-mail n’a pas encore été vérifié. Il s’agit d’une mesur #: src/view/com/posts/FollowingEmptyState.tsx:47 msgid "Your following feed is empty! Follow more users to see what's happening." -msgstr "Votre fil d’actu des comptes suivis est vide ! Suivez plus de comptes pour voir ce qui se passe." +msgstr "Votre fil d’actu des comptes suivis est vide ! Suivez plus de comptes pour voir ce qui se passe." #: src/view/com/auth/create/Step2.tsx:83 msgid "Your full handle will be" @@ -4879,19 +4585,13 @@ msgstr "Votre nom complet sera" msgid "Your full handle will be <0>@{0}</0>" msgstr "Votre pseudo complet sera <0>@{0}</0>" -#: src/view/screens/Settings.tsx:430 -#: src/view/shell/desktop/RightNav.tsx:137 -#: src/view/shell/Drawer.tsx:660 -#~ msgid "Your invite codes are hidden when logged in using an App Password" -#~ msgstr "Vos codes d’invitation sont cachés lorsque vous êtes connecté à l’aide d’un mot de passe d’application." - #: src/components/dialogs/MutedWords.tsx:221 msgid "Your muted words" -msgstr "" +msgstr "Vos mots masqués" #: src/view/com/modals/ChangePassword.tsx:155 msgid "Your password has been changed successfully!" -msgstr "" +msgstr "Votre mot de passe a été modifié avec succès !" #: src/view/com/composer/Composer.tsx:274 msgid "Your post has been published" diff --git a/src/locale/locales/it/messages.po b/src/locale/locales/it/messages.po index 29c6ba178..89f7b7106 100644 --- a/src/locale/locales/it/messages.po +++ b/src/locale/locales/it/messages.po @@ -1,11 +1,11 @@ msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: Italian localization\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-01-05 11:44+0530\n" -"PO-Revision-Date: \n" +"PO-Revision-Date: 2024-02-18\n" "Last-Translator: Gabriella Nonino <sandswimmer@gmail.com>\n" -"Language-Team: \n" +"Language-Team: Gabriella Nonino sandswimmer@gmail.com\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -32,21 +32,6 @@ msgstr "(no email)" msgid "{following} following" msgstr "{following} seguendo" -#: src/view/shell/desktop/RightNav.tsx:151 -#~ msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" -#~ msgstr "{invitesAvailable, plural, one {Codici d'invito: # available} other {Codici d'invito: # available}}" - -#: src/view/screens/Settings.tsx:NaN -#~ msgid "{invitesAvailable} invite code available" -#~ msgstr "{invitesAvailable} codice d'invito disponibile" - -#: src/view/screens/Settings.tsx:NaN -#~ msgid "{invitesAvailable} invite codes available" -#~ msgstr "{invitesAvailable} codici d'invito disponibili" - -#~ msgid "{message}" -#~ msgstr "{message}" - #: src/view/shell/Drawer.tsx:440 msgid "{numUnreadNotifications} unread" msgstr "{numUnreadNotifications} non letto" @@ -162,8 +147,7 @@ msgstr "Aggiungi account" msgid "Add alt text" msgstr "Aggiungi testo alternativo" -#: src/view/screens/AppPasswords.tsx:102 -#: src/view/screens/AppPasswords.tsx:143 +#: src/view/screens/AppPasswords.tsx:102 src/view/screens/AppPasswords.tsx:143 #: src/view/screens/AppPasswords.tsx:156 msgid "Add App Password" msgstr "Aggiungi la Password per l'App" @@ -237,12 +221,12 @@ msgstr "Avanzato" #: src/view/screens/Feeds.tsx:666 msgid "All the feeds you've saved, right in one place." -msgstr "" +msgstr "Tutti i feed che hai salvato, in un unico posto." #: src/view/com/auth/login/ForgotPasswordForm.tsx:221 #: src/view/com/modals/ChangePassword.tsx:168 msgid "Already have a code?" -msgstr "" +msgstr "Hai già un codice?" #: src/view/com/auth/login/ChooseAccountForm.tsx:98 msgid "Already signed in as @{0}" @@ -280,7 +264,7 @@ msgstr "e" #: src/screens/Onboarding/index.tsx:32 msgid "Animals" -msgstr "" +msgstr "Animali" #: src/view/screens/LanguageSettings.tsx:95 msgid "App Language" @@ -321,9 +305,6 @@ msgstr "Ricorso contro l'avviso sui contenuti" msgid "Appeal Content Warning" msgstr "Ricorso contro l'Avviso sui Contenuti" -#~ msgid "Appeal Decision" -#~ msgstr "Decisión de apelación" - #: src/view/com/util/moderation/LabelInfo.tsx:52 msgid "Appeal this decision" msgstr "Appella contro questa decisione" @@ -359,7 +340,7 @@ msgstr "Stai scrivendo in <0>{0}</0>?" #: src/screens/Onboarding/index.tsx:26 msgid "Art" -msgstr "" +msgstr "Arte" #: src/view/com/modals/SelfLabel.tsx:123 msgid "Artistic or non-erotic nudity." @@ -386,11 +367,11 @@ msgstr "Indietro" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:136 msgid "Based on your interest in {interestsText}" -msgstr "" +msgstr "Basato su i tuoi interessi {interestsText}" #: src/view/screens/Settings/index.tsx:523 msgid "Basics" -msgstr "Nozioni di base" +msgstr "Preferenze" #: src/view/com/auth/create/Step1.tsx:227 #: src/view/com/modals/BirthDateSettings.tsx:73 @@ -465,7 +446,7 @@ msgstr "Bluesky" #: src/view/com/auth/server-input/index.tsx:150 msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers." -msgstr "" +msgstr "Bluesky è una network aperto in cui puoi scegliere il tuo provider di hosting. L'hosting personalizzato adesso è disponibile in versione beta per i developers." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:80 @@ -490,13 +471,9 @@ msgstr "Bluesky è pubblico." 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 non mostrerà il tuo profilo e i tuoi post agli utenti disconnessi. Altre app potrebbero non rispettare questa richiesta. Questo non rende il tuo account privato." -#: src/view/com/modals/ServerInput.tsx:78 -#~ msgid "Bluesky.Social" -#~ msgstr "Bluesky.Social" - #: src/screens/Onboarding/index.tsx:33 msgid "Books" -msgstr "" +msgstr "Libri" #: src/view/screens/Settings/index.tsx:859 msgid "Build version {0} {1}" @@ -507,10 +484,6 @@ msgstr "Versione {0} {1}" msgid "Business" msgstr "Attività commerciale" -#: src/view/com/modals/ServerInput.tsx:115 -#~ msgid "Button disabled. Input custom domain to proceed." -#~ msgstr "Pulsante disabilitato. Inserisci il dominio personalizzato per procedere." - #: src/view/com/profile/ProfileSubpageHeader.tsx:157 msgid "by —" msgstr "da—" @@ -528,8 +501,7 @@ msgid "by you" msgstr "da te" #: src/view/com/composer/photos/OpenCameraBtn.tsx:60 -#: src/view/com/util/UserAvatar.tsx:224 -#: src/view/com/util/UserBanner.tsx:40 +#: src/view/com/util/UserAvatar.tsx:224 src/view/com/util/UserBanner.tsx:40 msgid "Camera" msgstr "Fotocamera" @@ -548,8 +520,7 @@ msgstr "Può contenere solo lettere, numeri, spazi, trattini e trattini bassi. D #: src/view/com/modals/EditImage.tsx:323 #: src/view/com/modals/EditProfile.tsx:249 #: src/view/com/modals/InAppBrowserConsent.tsx:78 -#: src/view/com/modals/LinkWarning.tsx:87 -#: src/view/com/modals/Repost.tsx:87 +#: src/view/com/modals/LinkWarning.tsx:87 src/view/com/modals/Repost.tsx:87 #: src/view/com/modals/VerifyEmail.tsx:247 #: src/view/com/modals/VerifyEmail.tsx:253 #: src/view/screens/Search/Search.tsx:716 @@ -557,8 +528,7 @@ msgstr "Può contenere solo lettere, numeri, spazi, trattini e trattini bassi. D msgid "Cancel" msgstr "Cancella" -#: src/view/com/modals/Confirm.tsx:88 -#: src/view/com/modals/Confirm.tsx:91 +#: src/view/com/modals/Confirm.tsx:88 src/view/com/modals/Confirm.tsx:91 #: src/view/com/modals/CreateOrEditList.tsx:360 #: src/view/com/modals/DeleteAccount.tsx:156 #: src/view/com/modals/DeleteAccount.tsx:234 @@ -571,9 +541,6 @@ msgstr "Cancella" msgid "Cancel account deletion" msgstr "Annulla la cancellazione dell'account" -#~ msgid "Cancel add image alt text" -#~ msgstr "Cancel·la afegir text a la imatge" - #: src/view/com/modals/ChangeHandle.tsx:149 msgid "Cancel change handle" msgstr "Annulla il cambio del tuo nome utente" @@ -604,9 +571,6 @@ msgctxt "action" msgid "Change" msgstr "Cambia" -#~ msgid "Change" -#~ msgstr "Canvia" - #: src/view/screens/Settings/index.tsx:696 msgid "Change handle" msgstr "Cambia il nome utente" @@ -622,11 +586,11 @@ msgstr "Cambia la mia email" #: src/view/screens/Settings/index.tsx:732 msgid "Change password" -msgstr "" +msgstr "Cambia la password" #: src/view/screens/Settings/index.tsx:741 msgid "Change Password" -msgstr "" +msgstr "Cambia la Password" #: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 msgid "Change post language to {0}" @@ -634,16 +598,15 @@ msgstr "Cambia la lingua del post a {0}" #: src/view/screens/Settings/index.tsx:733 msgid "Change your Bluesky password" -msgstr "" +msgstr "Cambia la tua password di Bluesky" #: src/view/com/modals/ChangeEmail.tsx:109 msgid "Change Your Email" msgstr "Cambia la tua email" -#: src/screens/Deactivated.tsx:72 -#: src/screens/Deactivated.tsx:76 +#: src/screens/Deactivated.tsx:72 src/screens/Deactivated.tsx:76 msgid "Check my status" -msgstr "" +msgstr "Verifica il mio stato" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:121 msgid "Check out some recommended feeds. Tap + to add them to your list of pinned feeds." @@ -671,7 +634,7 @@ msgstr "Scegli il servizio" #: src/screens/Onboarding/StepFinished.tsx:135 msgid "Choose the algorithms that power your custom feeds." -msgstr "" +msgstr "Scegli gli algoritmi che compilano i tuoi feed personalizzati." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:83 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:83 @@ -680,7 +643,7 @@ msgstr "Scegli gli algoritmi che alimentano la tua esperienza con feed personali #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 msgid "Choose your main feeds" -msgstr "" +msgstr "Scegli i tuoi feed principali" #: src/view/com/auth/create/Step1.tsx:196 msgid "Choose your password" @@ -723,12 +686,12 @@ msgstr "" #: src/screens/Onboarding/index.tsx:35 msgid "Climate" -msgstr "" +msgstr "Clima" #: src/view/com/modals/ChangePassword.tsx:265 #: src/view/com/modals/ChangePassword.tsx:268 msgid "Close" -msgstr "" +msgstr "Chiudi" #: src/components/Dialog/index.web.tsx:84 #: src/components/Dialog/index.web.tsx:198 @@ -781,11 +744,11 @@ msgstr "Comprime l'elenco degli utenti per una determinata notifica" #: src/screens/Onboarding/index.tsx:41 msgid "Comedy" -msgstr "" +msgstr "Commedia" #: src/screens/Onboarding/index.tsx:27 msgid "Comics" -msgstr "" +msgstr "Fumetti" #: src/Navigation.tsx:229 #: src/view/screens/CommunityGuidelines.tsx:32 @@ -794,7 +757,7 @@ msgstr "Linee guida della community" #: src/screens/Onboarding/StepFinished.tsx:148 msgid "Complete onboarding and start using your account" -msgstr "" +msgstr "Completa l'incorporazione e inizia a utilizzare il tuo account" #: src/view/com/auth/create/Step3.tsx:73 msgid "Complete the challenge" @@ -810,7 +773,7 @@ msgstr "Scrivi la risposta" #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:67 msgid "Configure content filtering setting for category: {0}" -msgstr "" +msgstr "Configura l'impostazione del filtro dei contenuti per la categoria:{0}" #: src/components/Prompt.tsx:124 #: src/view/com/modals/AppealLabel.tsx:98 @@ -822,8 +785,7 @@ msgstr "" msgid "Confirm" msgstr "Conferma" -#: src/view/com/modals/Confirm.tsx:75 -#: src/view/com/modals/Confirm.tsx:78 +#: src/view/com/modals/Confirm.tsx:75 src/view/com/modals/Confirm.tsx:78 msgctxt "action" msgid "Confirm" msgstr "Conferma" @@ -905,19 +867,19 @@ msgstr "Continua" #: src/screens/Onboarding/StepModeration/index.tsx:115 #: src/screens/Onboarding/StepTopicalFeeds.tsx:111 msgid "Continue to next step" -msgstr "" +msgstr "Vai al passaggio successivo" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:167 msgid "Continue to the next step" -msgstr "" +msgstr "Vai al passaggio successivo" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:191 msgid "Continue to the next step without following any accounts" -msgstr "" +msgstr "Vai al passaggio successivo senza seguire nessun account" #: src/screens/Onboarding/index.tsx:44 msgid "Cooking" -msgstr "" +msgstr "Cucina" #: src/view/com/modals/AddAppPasswords.tsx:195 #: src/view/com/modals/InviteCodes.tsx:182 @@ -1018,12 +980,12 @@ msgstr "Crea una scheda con una miniatura. La scheda si collega a {url}" #: src/screens/Onboarding/index.tsx:29 msgid "Culture" -msgstr "" +msgstr "Cultura" #: src/view/com/auth/server-input/index.tsx:95 #: src/view/com/auth/server-input/index.tsx:96 msgid "Custom" -msgstr "" +msgstr "Personalizzato" #: src/view/com/modals/ChangeHandle.tsx:389 msgid "Custom domain" @@ -1032,16 +994,12 @@ msgstr "Dominio personalizzato" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 #: src/view/screens/Feeds.tsx:692 msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." -msgstr "" +msgstr "I feed personalizzati creati dalla comunità ti offrono nuove esperienze e ti aiutano a trovare i contenuti che ami." #: src/view/screens/PreferencesExternalEmbeds.tsx:55 msgid "Customize media from external sites." msgstr "Personalizza i media da i siti esterni." -#: src/view/screens/Settings.tsx:687 -#~ msgid "Danger Zone" -#~ msgstr "Zona di Pericolo" - #: src/view/screens/Settings/index.tsx:485 #: src/view/screens/Settings/index.tsx:511 msgid "Dark" @@ -1053,7 +1011,7 @@ msgstr "Aspetto scuro" #: src/view/screens/Settings/index.tsx:498 msgid "Dark Theme" -msgstr "" +msgstr "Tema scuro" #: src/view/screens/Debug.tsx:83 msgid "Debug panel" @@ -1067,8 +1025,7 @@ msgstr "Eliminare l'account" msgid "Delete Account" msgstr "Eliminare l'Account" -#: src/view/screens/AppPasswords.tsx:222 -#: src/view/screens/AppPasswords.tsx:242 +#: src/view/screens/AppPasswords.tsx:222 src/view/screens/AppPasswords.tsx:242 msgid "Delete app password" msgstr "Elimina la password dell'app" @@ -1079,15 +1036,11 @@ msgstr "Elimina la lista" #: src/view/com/modals/DeleteAccount.tsx:223 msgid "Delete my account" -msgstr "Cancella il mio account" - -#: src/view/screens/Settings.tsx:706 -#~ msgid "Delete my account…" -#~ msgstr "Cancella il mio account…" +msgstr "Cancellare account" #: src/view/screens/Settings/index.tsx:784 msgid "Delete My Account…" -msgstr "" +msgstr "Cancellare Account…" #: src/view/com/util/forms/PostDropdownBtn.tsx:317 #: src/view/com/util/forms/PostDropdownBtn.tsx:326 @@ -1096,7 +1049,7 @@ msgstr "Elimina il post" #: src/view/com/util/forms/PostDropdownBtn.tsx:321 msgid "Delete this post?" -msgstr "Elimina questo post?" +msgstr "Eliminare questo post?" #: src/view/com/util/post-embeds/QuoteEmbed.tsx:70 msgid "Deleted" @@ -1126,7 +1079,7 @@ msgstr "Volevi dire qualcosa?" #: src/view/screens/Settings/index.tsx:504 msgid "Dim" -msgstr "" +msgstr "Fioco" #: src/view/com/composer/Composer.tsx:151 msgid "Discard" @@ -1143,15 +1096,11 @@ msgstr "Scoraggia le app dal mostrare il mio account agli utenti disconnessi" #: src/view/com/posts/FollowingEmptyState.tsx:74 #: src/view/com/posts/FollowingEndOfFeed.tsx:75 msgid "Discover new custom feeds" -msgstr "Scopri nuovi feed personalizzati" - -#: src/view/screens/Feeds.tsx:409 -#~ msgid "Discover new feeds" -#~ msgstr "Scopri nuovi feeds" +msgstr "Scopri nuovi feeds personalizzati" #: src/view/screens/Feeds.tsx:689 msgid "Discover New Feeds" -msgstr "" +msgstr "Scopri nuovi feeds" #: src/view/com/modals/EditProfile.tsx:192 msgid "Display name" @@ -1172,8 +1121,7 @@ msgstr "Dominio verificato!" #: src/view/com/auth/onboarding/RecommendedFollows.tsx:86 #: src/view/com/modals/EditImage.tsx:333 #: src/view/com/modals/ListAddRemoveUsers.tsx:144 -#: src/view/com/modals/SelfLabel.tsx:157 -#: src/view/com/modals/Threadgate.tsx:129 +#: src/view/com/modals/SelfLabel.tsx:157 src/view/com/modals/Threadgate.tsx:129 #: src/view/com/modals/Threadgate.tsx:132 #: src/view/com/modals/UserAddRemoveLists.tsx:95 #: src/view/com/modals/UserAddRemoveLists.tsx:98 @@ -1208,20 +1156,20 @@ msgstr "Usa il doppio tocco per accedere" #: src/view/screens/Settings/index.tsx:755 msgid "Download Bluesky account data (repository)" -msgstr "" +msgstr "Scarica i dati dell'account Bluesky (archivio)" #: src/view/screens/Settings/ExportCarDialog.tsx:59 #: src/view/screens/Settings/ExportCarDialog.tsx:63 msgid "Download CAR file" -msgstr "" +msgstr "Scarica il CAR file" #: src/view/com/composer/text-input/TextInput.web.tsx:249 msgid "Drop to add images" -msgstr "" +msgstr "Trascina e rilascia per aggiungere immagini" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:111 msgid "Due to Apple policies, adult content can only be enabled on the web after completing sign up." -msgstr "" +msgstr "A causa delle politiche di Apple, i contenuti per adulti possono essere abilitati sul Web solo dopo aver completato la registrazione." #: src/view/com/modals/EditProfile.tsx:185 msgid "e.g. Alice Roberts" @@ -1306,7 +1254,7 @@ msgstr "Modifica la descrizione del tuo profilo" #: src/screens/Onboarding/index.tsx:34 msgid "Education" -msgstr "" +msgstr "Formazione scolastica" #: src/view/com/auth/create/Step1.tsx:176 #: src/view/com/auth/login/ForgotPasswordForm.tsx:156 @@ -1347,7 +1295,7 @@ msgstr "Attiva Contenuto per Adulti" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:76 #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:77 msgid "Enable adult content in your feeds" -msgstr "" +msgstr "Abilita i contenuti per adulti nei tuoi feeds" #: src/view/com/modals/EmbedConsent.tsx:97 msgid "Enable External Media" @@ -1378,12 +1326,9 @@ msgstr "" msgid "Enter Confirmation Code" msgstr "Inserire il codice di conferma" -#~ msgid "Enter the address of your provider:" -#~ msgstr "Introdueix l'adreça del teu proveïdor:" - #: src/view/com/modals/ChangePassword.tsx:151 msgid "Enter the code you received to change your password." -msgstr "" +msgstr "Inserisci il codice che hai ricevuto per modificare la tua password." #: src/view/com/modals/ChangeHandle.tsx:371 msgid "Enter the domain you want to use" @@ -1462,12 +1407,12 @@ msgstr "Espandi o comprimi l'intero post a cui stai rispondendo" #: src/view/screens/Settings/index.tsx:753 msgid "Export my data" -msgstr "" +msgstr "Esporta i miei dati" #: src/view/screens/Settings/ExportCarDialog.tsx:44 #: src/view/screens/Settings/index.tsx:764 msgid "Export My Data" -msgstr "" +msgstr "Esporta i miei dati" #: src/view/com/modals/EmbedConsent.tsx:64 msgid "External Media" @@ -1512,7 +1457,7 @@ msgstr "Feed" #: src/view/com/feeds/FeedSourceCard.tsx:231 msgid "Feed by {0}" -msgstr "Feed realizzato da {0}" +msgstr "Feed fatto da {0}" #: src/view/screens/Feeds.tsx:605 msgid "Feed offline" @@ -1532,8 +1477,7 @@ msgstr "Commenti" #: src/view/screens/Feeds.tsx:524 #: src/view/screens/Profile.tsx:184 #: src/view/shell/bottom-bar/BottomBar.tsx:181 -#: src/view/shell/desktop/LeftNav.tsx:342 -#: src/view/shell/Drawer.tsx:476 +#: src/view/shell/desktop/LeftNav.tsx:342 src/view/shell/Drawer.tsx:476 #: src/view/shell/Drawer.tsx:477 msgid "Feeds" msgstr "Feeds" @@ -1548,11 +1492,11 @@ msgstr "I feed sono algoritmi personalizzati che gli utenti creano con un minimo #: src/screens/Onboarding/StepTopicalFeeds.tsx:76 msgid "Feeds can be topical as well!" -msgstr "" +msgstr "I feeds possono anche avere tematiche!" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Finalizing" -msgstr "" +msgstr "Finalizzando" #: src/view/com/posts/CustomFeedEmptyState.tsx:47 #: src/view/com/posts/FollowingEmptyState.tsx:57 @@ -1586,18 +1530,17 @@ msgstr "Ottimizza i la visualizzazione delle discussioni." #: src/screens/Onboarding/index.tsx:38 msgid "Fitness" -msgstr "" +msgstr "Fitness" #: src/screens/Onboarding/StepFinished.tsx:131 msgid "Flexible" -msgstr "" +msgstr "Flessibile" #: src/view/com/modals/EditImage.tsx:115 msgid "Flip horizontal" msgstr "Gira in orizzontale" -#: src/view/com/modals/EditImage.tsx:120 -#: src/view/com/modals/EditImage.tsx:287 +#: src/view/com/modals/EditImage.tsx:120 src/view/com/modals/EditImage.tsx:287 msgid "Flip vertically" msgstr "Gira in verticale" @@ -1620,11 +1563,11 @@ msgstr "Segui {0}" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:179 msgid "Follow All" -msgstr "" +msgstr "Segui tutti" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:174 msgid "Follow selected accounts and continue to the next step" -msgstr "" +msgstr "Segui gli account selezionati e vai al passaggio successivo" #: src/view/com/auth/onboarding/RecommendedFollows.tsx:64 msgid "Follow some users to get started. We can recommend you more users based on who you find interesting." @@ -1648,16 +1591,13 @@ msgstr "ti segue" #: src/view/screens/ProfileFollowers.tsx:25 msgid "Followers" -msgstr "Seguiti" - -#~ msgid "following" -#~ msgstr "seguint" +msgstr "Followers" #: src/view/com/post-thread/PostThreadFollowBtn.tsx:136 #: src/view/com/profile/ProfileHeader.tsx:495 #: src/view/screens/ProfileFollows.tsx:25 msgid "Following" -msgstr "Seguiti" +msgstr "Following" #: src/view/com/profile/ProfileHeader.tsx:149 msgid "Following {0}" @@ -1681,7 +1621,7 @@ msgstr "Ti Segue" #: src/screens/Onboarding/index.tsx:43 msgid "Food" -msgstr "" +msgstr "Gastronomia" #: src/view/com/modals/DeleteAccount.tsx:111 msgid "For security reasons, we'll need to send a confirmation code to your email address." @@ -1699,8 +1639,7 @@ msgstr "Dimenticato" msgid "Forgot password" msgstr "Ho dimenticato il password" -#: src/view/com/auth/login/Login.tsx:127 -#: src/view/com/auth/login/Login.tsx:143 +#: src/view/com/auth/login/Login.tsx:127 src/view/com/auth/login/Login.tsx:143 msgid "Forgot Password" msgstr "Ho dimenticato il Password" @@ -1723,8 +1662,7 @@ msgstr "Galleria" msgid "Get Started" msgstr "Inizia" -#: src/view/com/auth/LoggedOut.tsx:81 -#: src/view/com/auth/LoggedOut.tsx:82 +#: src/view/com/auth/LoggedOut.tsx:81 src/view/com/auth/LoggedOut.tsx:82 #: src/view/com/util/moderation/ScreenHider.tsx:123 #: src/view/shell/desktop/LeftNav.tsx:104 msgid "Go back" @@ -1737,10 +1675,9 @@ msgstr "Torna indietro" msgid "Go Back" msgstr "Torna Indietro" -#: src/screens/Onboarding/Layout.tsx:104 -#: src/screens/Onboarding/Layout.tsx:193 +#: src/screens/Onboarding/Layout.tsx:104 src/screens/Onboarding/Layout.tsx:193 msgid "Go back to previous step" -msgstr "" +msgstr "Torna al passaggio precedente" #: src/view/screens/Search/Search.tsx:747 #: src/view/shell/desktop/Search.tsx:262 @@ -1782,15 +1719,15 @@ msgstr "Aiuto" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:132 msgid "Here are some accounts for you to follow" -msgstr "" +msgstr "Ecco alcuni account da seguire" #: src/screens/Onboarding/StepTopicalFeeds.tsx:85 msgid "Here are some popular topical feeds. You can choose to follow as many as you like." -msgstr "" +msgstr "Ecco alcuni feed più visitati. Puoi seguire quanti ne vuoi." #: src/screens/Onboarding/StepTopicalFeeds.tsx:80 msgid "Here are some topical feeds based on your interests: {interestsText}. You can choose to follow as many as you like." -msgstr "" +msgstr "Ecco alcuni feed di attualità scelti in base ai tuoi interessi: {interestsText}. Puoi seguire quanti ne vuoi." #: src/view/com/modals/AddAppPasswords.tsx:153 msgid "Here is your app password." @@ -1859,8 +1796,7 @@ msgstr "Stiamo riscontrando problemi nel trovare questo feed. Potrebbe essere st msgid "Home" msgstr "Home" -#: src/Navigation.tsx:247 -#: src/view/com/pager/FeedsTabBarMobile.tsx:123 +#: src/Navigation.tsx:247 src/view/com/pager/FeedsTabBarMobile.tsx:123 #: src/view/screens/PreferencesHomeFeed.tsx:104 #: src/view/screens/Settings/index.tsx:543 #~ msgid "Home Feed Preferences" @@ -1871,9 +1807,6 @@ msgstr "Home" msgid "Hosting provider" msgstr "Servizio di hosting" -#~ msgid "Hosting provider address" -#~ msgstr "Adreça del proveïdor d'allotjament" - #: src/view/com/modals/InAppBrowserConsent.tsx:44 msgid "How should we open this link?" msgstr "Come dovremmo aprire questo link?" @@ -1900,7 +1833,7 @@ msgstr "Se niente è selezionato, adatto a tutte le età ." #: src/view/com/modals/ChangePassword.tsx:146 msgid "If you want to change your password, we will send you a code to verify that this is your account." -msgstr "" +msgstr "Se vuoi modificare la password, ti invieremo un codice per verificare se questo è il tuo account." #: src/view/com/util/images/Gallery.tsx:38 msgid "Image" @@ -1910,8 +1843,7 @@ msgstr "Immagine" msgid "Image alt text" msgstr "Testo alternativo dell'immagine" -#: src/view/com/util/UserAvatar.tsx:311 -#: src/view/com/util/UserBanner.tsx:118 +#: src/view/com/util/UserAvatar.tsx:311 src/view/com/util/UserBanner.tsx:118 msgid "Image options" msgstr "Opzioni per l'immagine" @@ -1979,10 +1911,6 @@ msgstr "Protocollo del post non valido o non supportato" msgid "Invalid username or password" msgstr "Nome dell'utente o password errato" -#: src/view/screens/Settings.tsx:411 -#~ msgid "Invite" -#~ msgstr "Invita" - #: src/view/com/modals/InviteCodes.tsx:93 msgid "Invite a Friend" msgstr "Invita un amico" @@ -2000,17 +1928,13 @@ msgstr "Codice invito non accettato. Controlla di averlo inserito correttamente msgid "Invite codes: {0} available" msgstr "Codici di invito: {0} disponibili" -#: src/view/shell/Drawer.tsx:645 -#~ msgid "Invite codes: {invitesAvailable} available" -#~ msgstr "Codici di invito: {invitesAvailable} disponibili" - #: src/view/com/modals/InviteCodes.tsx:169 msgid "Invite codes: 1 available" msgstr "Codici di invito: 1 disponibile" #: src/screens/Onboarding/StepFollowingFeed.tsx:64 msgid "It shows posts from the people you follow as they happen." -msgstr "" +msgstr "Mostra i post delle persone che segui." #: src/view/com/auth/HomeLoggedOutCTA.tsx:99 #: src/view/com/auth/SplashScreen.web.tsx:138 @@ -2032,7 +1956,7 @@ msgstr "Lavori" #: src/screens/Onboarding/index.tsx:24 msgid "Journalism" -msgstr "" +msgstr "Giornalismo" #: src/view/com/composer/select-language/SelectLangBtn.tsx:104 msgid "Language selection" @@ -2087,23 +2011,21 @@ msgstr "Stai lasciando Bluesky" #: src/screens/Deactivated.tsx:128 msgid "left to go." -msgstr "" +msgstr "mancano." #: src/view/screens/Settings/index.tsx:278 msgid "Legacy storage cleared, you need to restart the app now." msgstr "L'archivio legacy è stato cancellato, riattiva la app." -#: src/view/com/auth/login/Login.tsx:128 -#: src/view/com/auth/login/Login.tsx:144 +#: src/view/com/auth/login/Login.tsx:128 src/view/com/auth/login/Login.tsx:144 msgid "Let's get your password reset!" msgstr "Reimpostazione della password!" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Let's go!" -msgstr "" +msgstr "Andiamo!" -#: src/view/com/util/UserAvatar.tsx:248 -#: src/view/com/util/UserBanner.tsx:62 +#: src/view/com/util/UserAvatar.tsx:248 src/view/com/util/UserBanner.tsx:62 msgid "Library" msgstr "Biblioteca" @@ -2121,32 +2043,28 @@ msgstr "Metti mi piace a questo feed" #: src/Navigation.tsx:199 msgid "Liked by" -msgstr "Piaciuto a" +msgstr "Piace a" #: src/view/screens/PostLikedBy.tsx:27 #: src/view/screens/ProfileFeedLikedBy.tsx:27 msgid "Liked By" -msgstr "" +msgstr "Piace A" #: src/view/com/feeds/FeedSourceCard.tsx:279 msgid "Liked by {0} {1}" -msgstr "È piaciuto a {0} {1}" +msgstr "Piace a {0} {1}" #: src/view/screens/ProfileFeed.tsx:606 msgid "Liked by {likeCount} {0}" -msgstr "È piaciuto a {likeCount} {0}" +msgstr "Piace a {likeCount} {0}" #: src/view/com/notifications/FeedItem.tsx:170 msgid "liked your custom feed" -msgstr "" - -#: src/view/com/notifications/FeedItem.tsx:171 -#~ msgid "liked your custom feed{0}" -#~ msgstr "è piaciuto il feed personalizzato{0}" +msgstr "piace il tuo feed personalizzato" #: src/view/com/notifications/FeedItem.tsx:155 msgid "liked your post" -msgstr "è piaciuto il tuo post" +msgstr "piace il tuo post" #: src/view/screens/Profile.tsx:183 msgid "Likes" @@ -2228,24 +2146,19 @@ msgstr "Caricamento..." msgid "Log" msgstr "Log" -#: src/screens/Deactivated.tsx:149 -#: src/screens/Deactivated.tsx:152 -#: src/screens/Deactivated.tsx:178 -#: src/screens/Deactivated.tsx:181 +#: src/screens/Deactivated.tsx:149 src/screens/Deactivated.tsx:152 +#: src/screens/Deactivated.tsx:178 src/screens/Deactivated.tsx:181 msgid "Log out" -msgstr "" +msgstr "Disconnetta l'account" #: src/view/screens/Moderation.tsx:155 msgid "Logged-out visibility" -msgstr "Visibilità degli utenti non connettati" +msgstr "Visibilità degli utenti disconnessi" #: src/view/com/auth/login/ChooseAccountForm.tsx:133 msgid "Login to account that is not listed" msgstr "Accedi all'account che non è nella lista" -#~ msgid "Looks like this feed is only available to users with a Bluesky account. Please sign up or sign in to view this feed!" -#~ msgstr "Parece que este canal de noticias sólo está disponible para usuarios con una cuenta Bluesky. Por favor, ¡regÃstrate o inicia sesión para ver este canal!" - #: src/view/com/modals/LinkWarning.tsx:65 msgid "Make sure this is where you intend to go!" msgstr "Assicurati che questo sia dove intendi andare!" @@ -2279,9 +2192,6 @@ msgstr "Utenti menzionati" msgid "Menu" msgstr "Menù" -#~ msgid "Message from server" -#~ msgstr "Missatge del servidor" - #: src/view/com/posts/FeedErrorMessage.tsx:197 msgid "Message from server: {0}" msgstr "Messaggio dal server: {0}" @@ -2289,8 +2199,7 @@ msgstr "Messaggio dal server: {0}" #: src/Navigation.tsx:117 #: src/view/screens/Moderation.tsx:66 #: src/view/screens/Settings/index.tsx:625 -#: src/view/shell/desktop/LeftNav.tsx:397 -#: src/view/shell/Drawer.tsx:511 +#: src/view/shell/desktop/LeftNav.tsx:397 src/view/shell/Drawer.tsx:511 #: src/view/shell/Drawer.tsx:512 msgid "Moderation" msgstr "Moderazione" @@ -2462,7 +2371,7 @@ msgstr "I miei Feeds Salvati" #: src/view/com/auth/server-input/index.tsx:118 msgid "my-server.com" -msgstr "" +msgstr "my-server.com" #: src/view/com/modals/AddAppPasswords.tsx:179 #: src/view/com/modals/CreateOrEditList.tsx:290 @@ -2475,7 +2384,7 @@ msgstr "Il nome è obbligatorio" #: src/screens/Onboarding/index.tsx:25 msgid "Nature" -msgstr "" +msgstr "Natura" #: src/view/com/auth/login/ForgotPasswordForm.tsx:190 #: src/view/com/auth/login/ForgotPasswordForm.tsx:219 @@ -2501,7 +2410,7 @@ msgstr "Non perdere mai l'accesso ai tuoi follower e ai tuoi dati." #: src/screens/Onboarding/StepFinished.tsx:119 msgid "Never lose access to your followers or data." -msgstr "" +msgstr "Non perdere mai l'accesso ai tuoi follower o ai tuoi dati." #: src/components/dialogs/MutedWords.tsx:293 msgid "Nevermind" @@ -2510,11 +2419,11 @@ msgstr "" #: src/view/screens/Lists.tsx:76 msgctxt "action" msgid "New" -msgstr "Nuovo" +msgstr "Nuova" #: src/view/screens/ModerationModlists.tsx:78 msgid "New" -msgstr "Nuovo" +msgstr "Nuova" #: src/view/com/modals/CreateOrEditList.tsx:252 msgid "New Moderation List" @@ -2526,7 +2435,7 @@ msgstr "Nuovo Password" #: src/view/com/modals/ChangePassword.tsx:215 msgid "New Password" -msgstr "" +msgstr "Nuovo Password" #: src/view/com/feeds/FeedPage.tsx:126 msgctxt "action" @@ -2548,12 +2457,9 @@ msgctxt "action" msgid "New Post" msgstr "Nuovo post" -#~ msgid "New Post" -#~ msgstr "Nova publicació" - #: src/view/com/modals/CreateOrEditList.tsx:247 msgid "New User List" -msgstr "Nuova lista utenti" +msgstr "Nuova lista" #: src/view/screens/PreferencesThreads.tsx:79 msgid "Newest replies first" @@ -2561,7 +2467,7 @@ msgstr "Mostrare prima le risposte più recenti" #: src/screens/Onboarding/index.tsx:23 msgid "News" -msgstr "" +msgstr "Notizie" #: src/view/com/auth/create/CreateAccount.tsx:172 #: src/view/com/auth/login/ForgotPasswordForm.tsx:182 @@ -2655,11 +2561,10 @@ msgstr "Nota: Bluesky è una rete aperta e pubblica. Questa impostazione limita #: src/view/screens/Notifications.tsx:124 #: src/view/screens/Notifications.tsx:148 #: src/view/shell/bottom-bar/BottomBar.tsx:205 -#: src/view/shell/desktop/LeftNav.tsx:361 -#: src/view/shell/Drawer.tsx:435 +#: src/view/shell/desktop/LeftNav.tsx:361 src/view/shell/Drawer.tsx:435 #: src/view/shell/Drawer.tsx:436 msgid "Notifications" -msgstr "Notifica" +msgstr "Notifiche" #: src/view/com/modals/SelfLabel.tsx:103 msgid "Nudity" @@ -2671,7 +2576,7 @@ msgstr "Oh no!" #: src/screens/Onboarding/StepInterests/index.tsx:128 msgid "Oh no! Something went wrong." -msgstr "" +msgstr "Oh no! Qualcosa è andato storto." #: src/view/com/auth/login/PasswordUpdatedForm.tsx:41 msgid "Okay" @@ -2679,7 +2584,7 @@ msgstr "Va bene" #: src/view/screens/PreferencesThreads.tsx:78 msgid "Oldest replies first" -msgstr "Prima le risposte più vecchie" +msgstr "Mostrare prima le risposte più vecchie" #: src/view/screens/Settings/index.tsx:234 msgid "Onboarding reset" @@ -2705,7 +2610,7 @@ msgstr "Ops!" #: src/screens/Onboarding/StepFinished.tsx:115 msgid "Open" -msgstr "" +msgstr "Apri" #: src/view/screens/Moderation.tsx:75 msgid "Open content filtering settings" @@ -2780,10 +2685,6 @@ msgstr "Apre la lista dei followers" msgid "Opens following list" msgstr "Apre la lista di chi segui" -#: src/view/screens/Settings.tsx:412 -#~ msgid "Opens invite code list" -#~ msgstr "Apre la lista dei codici di invito" - #: src/view/com/modals/InviteCodes.tsx:172 msgid "Opens list of invite codes" msgstr "Apre la lista dei codici di invito" @@ -2845,10 +2746,6 @@ msgstr "Oppure combina queste opzioni:" msgid "Other account" msgstr "Altro account" -#: src/view/com/modals/ServerInput.tsx:88 -#~ msgid "Other service" -#~ msgstr "Altro servizio" - #: src/view/com/composer/select-language/SelectLangBtn.tsx:91 msgid "Other..." msgstr "Altro..." @@ -2860,7 +2757,7 @@ msgstr "Pagina non trovata" #: src/view/screens/NotFound.tsx:42 msgid "Page Not Found" -msgstr "" +msgstr "Pagina non trovata" #: src/view/com/auth/create/Step1.tsx:191 #: src/view/com/auth/create/Step1.tsx:201 @@ -2896,7 +2793,7 @@ msgstr "L'autorizzazione per accedere la cartella delle immagini è stata negata #: src/screens/Onboarding/index.tsx:31 msgid "Pets" -msgstr "" +msgstr "Animali di compagnia" #: src/view/com/auth/create/Step2.tsx:183 #~ msgid "Phone number" @@ -2909,11 +2806,11 @@ msgstr "Immagini per adulti." #: src/view/screens/ProfileFeed.tsx:354 #: src/view/screens/ProfileList.tsx:581 msgid "Pin to home" -msgstr "Fissa sulla pagina principale" +msgstr "Fissa sulla home page" #: src/view/screens/SavedFeeds.tsx:88 msgid "Pinned Feeds" -msgstr "Feed Fissati" +msgstr "Feeds Fissi" #: src/view/com/util/post-embeds/ExternalGifEmbed.tsx:111 msgid "Play {0}" @@ -2981,9 +2878,6 @@ msgstr "Inserisci anche la tua password:" msgid "Please tell us why you think this content warning was incorrectly applied!" msgstr "Spiegaci perché ritieni che questo avviso sui contenuti sia stato applicato in modo errato!" -#~ msgid "Please tell us why you think this decision was incorrect." -#~ msgstr "Por favor, dinos por qué crees que esta decisión fue incorrecta." - #: src/view/com/modals/VerifyEmail.tsx:101 msgid "Please Verify Your Email" msgstr "Verifica la tua email" @@ -2994,7 +2888,7 @@ msgstr "Attendi il caricamento della scheda di collegamento" #: src/screens/Onboarding/index.tsx:37 msgid "Politics" -msgstr "" +msgstr "Politica" #: src/view/com/modals/SelfLabel.tsx:111 msgid "Porn" @@ -3093,10 +2987,8 @@ msgid "Processing..." msgstr "Elaborazione in corso…" #: src/view/shell/bottom-bar/BottomBar.tsx:247 -#: src/view/shell/desktop/LeftNav.tsx:415 -#: src/view/shell/Drawer.tsx:70 -#: src/view/shell/Drawer.tsx:546 -#: src/view/shell/Drawer.tsx:547 +#: src/view/shell/desktop/LeftNav.tsx:415 src/view/shell/Drawer.tsx:70 +#: src/view/shell/Drawer.tsx:546 src/view/shell/Drawer.tsx:547 msgid "Profile" msgstr "Profilo" @@ -3110,7 +3002,7 @@ msgstr "Proteggi il tuo account verificando la tua email." #: src/screens/Onboarding/StepFinished.tsx:101 msgid "Public" -msgstr "" +msgstr "Pubblico" #: src/view/screens/ModerationModlists.tsx:61 msgid "Public, shareable lists of users to mute or block in bulk." @@ -3118,7 +3010,7 @@ msgstr "Elenchi pubblici e condivisibili di utenti da disattivare o bloccare in #: src/view/screens/Lists.tsx:61 msgid "Public, shareable lists which can drive feeds." -msgstr "Elenchi pubblici e condivisibili che possono gestire i feeds." +msgstr "Liste pubbliche e condivisibili che possono impulsare i feeds." #: src/view/com/composer/Composer.tsx:342 msgid "Publish post" @@ -3142,9 +3034,6 @@ msgctxt "action" msgid "Quote Post" msgstr "Cita il post" -#~ msgid "Quote Post" -#~ msgstr "Cita una publicació" - #: src/view/screens/PreferencesThreads.tsx:86 msgid "Random (aka \"Poster's Roulette\")" msgstr "Selezione a caso (nota anche come \"Poster's Roulette\")" @@ -3165,8 +3054,7 @@ msgstr "Utenti consigliati" #: src/view/com/modals/ListAddRemoveUsers.tsx:264 #: src/view/com/modals/SelfLabel.tsx:83 #: src/view/com/modals/UserAddRemoveLists.tsx:219 -#: src/view/com/util/UserAvatar.tsx:285 -#: src/view/com/util/UserBanner.tsx:91 +#: src/view/com/util/UserAvatar.tsx:285 src/view/com/util/UserBanner.tsx:91 msgid "Remove" msgstr "Rimuovi" @@ -3240,7 +3128,7 @@ msgstr "Le risposte a questo thread sono disabilitate" #: src/view/com/composer/Composer.tsx:355 msgctxt "action" msgid "Reply" -msgstr "Rispondi" +msgstr "Risposta" #: src/view/screens/PreferencesFollowingFeed.tsx:144 msgid "Reply Filters" @@ -3250,7 +3138,7 @@ msgstr "Filtri di risposta" #: src/view/com/posts/FeedItem.tsx:287 msgctxt "description" msgid "Reply to <0/>" -msgstr "Rispondi a <0/>" +msgstr "In risposta a <0/>" #: src/view/com/modals/report/Modal.tsx:166 msgid "Report {collectionName}" @@ -3274,8 +3162,7 @@ msgstr "Segnala la lista" msgid "Report post" msgstr "Segnala il post" -#: src/view/com/modals/Repost.tsx:43 -#: src/view/com/modals/Repost.tsx:48 +#: src/view/com/modals/Repost.tsx:43 src/view/com/modals/Repost.tsx:48 #: src/view/com/modals/Repost.tsx:53 #: src/view/com/util/post-ctrls/RepostButton.tsx:61 msgctxt "action" @@ -3292,32 +3179,24 @@ msgid "Repost or quote post" msgstr "Ripubblicare o citare il post" #: src/view/screens/PostRepostedBy.tsx:27 -#~ msgid "Reposted by" -#~ msgstr "Ripubblicato da" - -#: src/view/screens/PostRepostedBy.tsx:27 msgid "Reposted By" -msgstr "" +msgstr "Repost di" #: src/view/com/posts/FeedItem.tsx:207 msgid "Reposted by {0}" -msgstr "" - -#: src/view/com/posts/FeedItem.tsx:206 -#~ msgid "Reposted by {0})" -#~ msgstr "Ripubblicato da {0})" +msgstr "Repost di {0}" #: src/view/com/posts/FeedItem.tsx:224 msgid "Reposted by <0/>" -msgstr "Ripubblicato da <0/>" +msgstr "Repost di <0/>" #: src/view/com/notifications/FeedItem.tsx:162 msgid "reposted your post" -msgstr "ripubblicato il tuo post" +msgstr "reposted il tuo post" #: src/view/com/post-thread/PostThreadItem.tsx:188 msgid "Reposts of this post" -msgstr "Ripubblicazione di questo post" +msgstr "Repost di questo post" #: src/view/com/modals/ChangeEmail.tsx:181 #: src/view/com/modals/ChangeEmail.tsx:183 @@ -3331,7 +3210,7 @@ msgstr "Richiedi un cambio" #: src/view/com/modals/ChangePassword.tsx:239 #: src/view/com/modals/ChangePassword.tsx:241 msgid "Request Code" -msgstr "" +msgstr "Richiedi il codice" #: src/view/screens/Settings/index.tsx:456 msgid "Require alt text before posting" @@ -3348,7 +3227,7 @@ msgstr "Reimpostare il codice" #: src/view/com/modals/ChangePassword.tsx:190 msgid "Reset Code" -msgstr "" +msgstr "Reimposta il Codice" #: src/view/screens/Settings/index.tsx:824 msgid "Reset onboarding" @@ -3455,7 +3334,7 @@ msgstr "Salva la modifica del cambio dell'utente in {handle}" #: src/screens/Onboarding/index.tsx:36 msgid "Science" -msgstr "" +msgstr "Scienza" #: src/view/screens/ProfileList.tsx:859 msgid "Scroll to top" @@ -3470,10 +3349,8 @@ msgstr "Scorri verso l'alto" #: src/view/screens/Search/Search.tsx:668 #: src/view/screens/Search/Search.tsx:686 #: src/view/shell/bottom-bar/BottomBar.tsx:159 -#: src/view/shell/desktop/LeftNav.tsx:324 -#: src/view/shell/desktop/Search.tsx:214 -#: src/view/shell/desktop/Search.tsx:223 -#: src/view/shell/Drawer.tsx:362 +#: src/view/shell/desktop/LeftNav.tsx:324 src/view/shell/desktop/Search.tsx:214 +#: src/view/shell/desktop/Search.tsx:223 src/view/shell/Drawer.tsx:362 #: src/view/shell/Drawer.tsx:363 msgid "Search" msgstr "Cerca" @@ -3545,10 +3422,6 @@ msgstr "Scopri cosa c'è dopo" msgid "Select {item}" msgstr "Seleziona {item}" -#: src/view/com/modals/ServerInput.tsx:75 -#~ msgid "Select Bluesky Social" -#~ msgstr "Seleziona Bluesky Social" - #: src/view/com/auth/login/Login.tsx:117 msgid "Select from an existing account" msgstr "Seleziona da un account esistente" @@ -3564,19 +3437,19 @@ msgstr "Selecciona el servei" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:52 msgid "Select some accounts below to follow" -msgstr "" +msgstr "Seleziona alcuni account da seguire qui giù" #: src/view/com/auth/server-input/index.tsx:82 msgid "Select the service that hosts your data." -msgstr "" +msgstr "Seleziona il servizio che ospita i tuoi dati." #: src/screens/Onboarding/StepTopicalFeeds.tsx:96 msgid "Select topical feeds to follow from the list below" -msgstr "" +msgstr "Seleziona i feeds con temi da seguire dal seguente elenco" #: src/screens/Onboarding/StepModeration/index.tsx:75 msgid "Select what you want to see (or not see), and we’ll handle the rest." -msgstr "" +msgstr "Seleziona ciò che vuoi vedere (o non vedere) e noi gestiremo il resto." #: src/view/screens/LanguageSettings.tsx:281 msgid "Select which languages you want your subscribed feeds to include. If none are selected, all languages will be shown." @@ -3588,7 +3461,7 @@ msgstr "Seleziona la lingua dell'app per il testo predefinito da visualizzare ne #: src/screens/Onboarding/StepInterests/index.tsx:196 msgid "Select your interests from the options below" -msgstr "" +msgstr "Seleziona i tuoi interessi dalle seguenti opzioni" #: src/view/com/auth/create/Step2.tsx:155 #~ msgid "Select your phone's country" @@ -3600,11 +3473,11 @@ msgstr "Seleziona la tua lingua preferita per le traduzioni nel tuo feed." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:116 msgid "Select your primary algorithmic feeds" -msgstr "" +msgstr "Seleziona i tuoi feed algoritmici principali" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:142 msgid "Select your secondary algorithmic feeds" -msgstr "" +msgstr "Seleziona i tuoi feed algoritmici secondari" #: src/view/com/modals/VerifyEmail.tsx:202 #: src/view/com/modals/VerifyEmail.tsx:204 @@ -3620,11 +3493,7 @@ msgctxt "action" msgid "Send Email" msgstr "Invia email" -#~ msgid "Send Email" -#~ msgstr "Envia correu" - -#: src/view/shell/Drawer.tsx:295 -#: src/view/shell/Drawer.tsx:316 +#: src/view/shell/Drawer.tsx:295 src/view/shell/Drawer.tsx:316 msgid "Send feedback" msgstr "Invia feedback" @@ -3638,7 +3507,7 @@ msgstr "Invia un'email con il codice di conferma per la cancellazione dell'accou #: src/view/com/auth/server-input/index.tsx:110 msgid "Server address" -msgstr "" +msgstr "Indirizzo del server" #: src/view/com/modals/ContentFilteringSettings.tsx:311 msgid "Set {value} for {labelGroup} content moderation policy" @@ -3664,11 +3533,11 @@ msgstr "Imposta il colore del tema basato sulle impostazioni del tuo sistema" #: src/view/screens/Settings/index.tsx:514 msgid "Set dark theme to the dark theme" -msgstr "" +msgstr "Imposta il tema scuro sul tema scuro" #: src/view/screens/Settings/index.tsx:507 msgid "Set dark theme to the dim theme" -msgstr "" +msgstr "Imposta il tema scuro sul tema scuro" #: src/view/com/auth/login/SetNewPasswordForm.tsx:104 msgid "Set new password" @@ -3704,7 +3573,7 @@ msgstr "" #: src/screens/Onboarding/Layout.tsx:50 msgid "Set up your account" -msgstr "" +msgstr "Configura il tuo account" #: src/view/com/modals/ChangeHandle.tsx:266 msgid "Sets Bluesky username" @@ -3792,15 +3661,15 @@ msgstr "Mostra post con citazioni" #: src/screens/Onboarding/StepFollowingFeed.tsx:118 msgid "Show quote-posts in Following feed" -msgstr "" +msgstr "Mostra i post con citazioni nel feed Seguiti" #: src/screens/Onboarding/StepFollowingFeed.tsx:134 msgid "Show quotes in Following" -msgstr "" +msgstr "Mostra le citazioni in Seguiti" #: src/screens/Onboarding/StepFollowingFeed.tsx:94 msgid "Show re-posts in Following feed" -msgstr "" +msgstr "Mostra re-post nel feed Seguiti" #: src/view/screens/PreferencesFollowingFeed.tsx:119 msgid "Show Replies" @@ -3812,11 +3681,11 @@ msgstr "Mostra le risposte delle persone che segui prima delle altre risposte." #: src/screens/Onboarding/StepFollowingFeed.tsx:86 msgid "Show replies in Following" -msgstr "" +msgstr "Mostra le risposte in Seguiti" #: src/screens/Onboarding/StepFollowingFeed.tsx:70 msgid "Show replies in Following feed" -msgstr "" +msgstr "Mostra le risposte nel feed Seguiti" #: src/view/screens/PreferencesFollowingFeed.tsx:70 msgid "Show replies with at least {value} {0}" @@ -3828,7 +3697,7 @@ msgstr "Mostra ripubblicazioni" #: src/screens/Onboarding/StepFollowingFeed.tsx:110 msgid "Show reposts in Following" -msgstr "" +msgstr "Mostra i re-repost in Seguiti" #: src/view/com/util/moderation/ContentHider.tsx:67 #: src/view/com/util/moderation/PostHider.tsx:61 @@ -3849,16 +3718,14 @@ msgid "Shows posts from {0} in your feed" msgstr "Mostra i post di {0} nel tuo feed" #: src/view/com/auth/HomeLoggedOutCTA.tsx:70 -#: src/view/com/auth/login/Login.tsx:98 -#: src/view/com/auth/SplashScreen.tsx:79 +#: src/view/com/auth/login/Login.tsx:98 src/view/com/auth/SplashScreen.tsx:79 #: src/view/shell/bottom-bar/BottomBar.tsx:285 #: src/view/shell/bottom-bar/BottomBar.tsx:286 #: src/view/shell/bottom-bar/BottomBar.tsx:288 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:178 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:179 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:181 -#: src/view/shell/NavSignupCard.tsx:58 -#: src/view/shell/NavSignupCard.tsx:59 +#: src/view/shell/NavSignupCard.tsx:58 src/view/shell/NavSignupCard.tsx:59 msgid "Sign in" msgstr "Accedi" @@ -3886,7 +3753,7 @@ msgstr "Accedere a" #: src/view/screens/Settings/index.tsx:100 #: src/view/screens/Settings/index.tsx:103 msgid "Sign out" -msgstr "Disconnettiti" +msgstr "Disconnetta" #: src/view/shell/bottom-bar/BottomBar.tsx:275 #: src/view/shell/bottom-bar/BottomBar.tsx:276 @@ -3894,8 +3761,7 @@ msgstr "Disconnettiti" #: src/view/shell/bottom-bar/BottomBarWeb.tsx:168 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:169 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:171 -#: src/view/shell/NavSignupCard.tsx:49 -#: src/view/shell/NavSignupCard.tsx:50 +#: src/view/shell/NavSignupCard.tsx:49 src/view/shell/NavSignupCard.tsx:50 #: src/view/shell/NavSignupCard.tsx:52 msgid "Sign up" msgstr "Iscrizione" @@ -3928,7 +3794,7 @@ msgstr "Salta questo passo" #: src/screens/Onboarding/StepInterests/index.tsx:232 msgid "Skip this flow" -msgstr "" +msgstr "Salta questa corrente" #: src/view/com/auth/create/Step2.tsx:82 #~ msgid "SMS verification" @@ -3936,7 +3802,7 @@ msgstr "" #: src/screens/Onboarding/index.tsx:40 msgid "Software Dev" -msgstr "" +msgstr "Sviluppo Software" #: src/view/com/modals/ProfilePreview.tsx:62 #~ msgid "Something went wrong and we're not sure what." @@ -3964,16 +3830,12 @@ msgstr "Ordina le risposte allo stesso post per:" #: src/screens/Onboarding/index.tsx:30 msgid "Sports" -msgstr "" +msgstr "Sports" #: src/view/com/modals/crop-image/CropImage.web.tsx:122 msgid "Square" msgstr "Quadrato" -#: src/view/com/modals/ServerInput.tsx:62 -#~ msgid "Staging" -#~ msgstr "Allestimento" - #: src/view/screens/Settings/index.tsx:871 msgid "Status page" msgstr "Pagina di stato" @@ -4002,7 +3864,7 @@ msgstr "Iscriviti" #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:173 #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:308 msgid "Subscribe to the {0} feed" -msgstr "" +msgstr "Iscriviti a {0} feed" #: src/view/screens/ProfileList.tsx:604 msgid "Subscribe to this list" @@ -4026,10 +3888,6 @@ msgstr "Suggestivo" msgid "Support" msgstr "Supporto" -#: src/view/com/modals/ProfilePreview.tsx:110 -#~ msgid "Swipe up to see more" -#~ msgstr "Scorri verso l'alto per vedere di più" - #: src/view/com/modals/SwitchAccount.tsx:117 msgid "Switch Account" msgstr "Cambia account" @@ -4074,7 +3932,7 @@ msgstr "Tocca per visualizzare completamente" #: src/screens/Onboarding/index.tsx:39 msgid "Tech" -msgstr "" +msgstr "Tecnologia" #: src/view/shell/desktop/RightNav.tsx:81 msgid "Terms" @@ -4114,7 +3972,7 @@ msgstr "La politica sul copyright è stata spostata a <0/>" #: src/screens/Onboarding/Layout.tsx:60 msgid "The following steps will help customize your Bluesky experience." -msgstr "" +msgstr "I passaggi seguenti ti aiuteranno a personalizzare la tua esperienza con Bluesky." #: src/view/com/post-thread/PostThread.tsx:517 msgid "The post may have been deleted." @@ -4128,16 +3986,13 @@ msgstr "La politica sulla privacy è stata spostata a <0/><0/>" 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 "Il modulo di supporto è stato spostato. Se hai bisogno di aiuto, <0/> o visita {HELP_DESK_URL} per metterti in contatto con noi." -#~ 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 "El formulari de suport ha estat traslladat. Si necessites ajuda, <0/> o visita {HELP_DESK_URL} per contactar amb nosaltres." - #: src/view/screens/TermsOfService.tsx:33 msgid "The Terms of Service have been moved to" msgstr "I Termini di Servizio sono stati spostati a" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:150 msgid "There are many feeds to try:" -msgstr "" +msgstr "Ci sono molti feed da provare:" #: src/view/screens/ProfileFeed.tsx:550 msgid "There was an an issue contacting the server, please check your internet connection and try again." @@ -4217,7 +4072,7 @@ msgstr "Si è verificato un problema imprevisto nell'applicazione. Per favore fa #: src/screens/Deactivated.tsx:106 msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." -msgstr "" +msgstr "C'è stata un'ondata di nuovi utenti su Bluesky! Attiveremo il tuo account il prima possibile." #: src/view/com/auth/create/Step2.tsx:55 #~ msgid "There's something wrong with this number. Please choose your country and enter your full phone number!" @@ -4225,10 +4080,7 @@ msgstr "" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:138 msgid "These are popular accounts you might like:" -msgstr "" - -#~ msgid "This {0} has been labeled." -#~ msgstr "Este {0} ha sido etiquetado." +msgstr "Questi sono gli account popolari che potrebbero piacerti:" #: src/view/com/util/moderation/ScreenHider.tsx:88 msgid "This {screenDescription} has been flagged:" @@ -4252,7 +4104,7 @@ msgstr "Questo contenuto non è visualizzabile senza un account Bluesky." #: src/view/screens/Settings/ExportCarDialog.tsx:75 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost.</0>" -msgstr "" +msgstr "Questa funzionalità è in versione beta. Puoi leggere ulteriori informazioni sulle esportazioni dell' archivio in <0>questo post del blog.</0>" #: src/view/com/posts/FeedErrorMessage.tsx:114 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." @@ -4276,9 +4128,6 @@ msgstr "Queste informazioni non vengono condivise con altri utenti." msgid "This is important in case you ever need to change your email or reset your password." msgstr "Questo è importante nel caso in cui avessi bisogno di modificare la tua email o reimpostare la password." -#~ msgid "This is the service that keeps you online." -#~ msgstr "Aquest és el servei que et manté connectat." - #: src/view/com/modals/LinkWarning.tsx:58 msgid "This link is taking you to the following website:" msgstr "Questo link ti porta al seguente sito web:" @@ -4305,11 +4154,7 @@ msgstr "Questo utente è incluso nell'elenco <0/> che hai bloccato." #: src/view/com/modals/ModerationDetails.tsx:74 msgid "This user is included in the <0/> list which you have muted." -msgstr "" - -#: src/view/com/modals/ModerationDetails.tsx:74 -#~ msgid "This user is included the <0/> list which you have muted." -#~ msgstr "Questo utente è incluso nell'elenco <0/> che hai silenziato." +msgstr "Questo utente è incluso nell'elenco <0/> che hai disattivato." #: src/view/com/modals/SelfLabel.tsx:137 msgid "This warning is only available for posts with media attached." @@ -4394,8 +4239,7 @@ msgstr "Sblocca" msgid "Unblock Account" msgstr "Sblocca il conto" -#: src/view/com/modals/Repost.tsx:42 -#: src/view/com/modals/Repost.tsx:55 +#: src/view/com/modals/Repost.tsx:42 src/view/com/modals/Repost.tsx:55 #: src/view/com/util/post-ctrls/RepostButton.tsx:60 #: src/view/com/util/post-ctrls/RepostButton.web.tsx:48 msgid "Undo repost" @@ -4495,10 +4339,6 @@ msgstr "Utilizza il mio browser predefinito" msgid "Use this to sign into the other app along with your handle." msgstr "Utilizza questo per accedere all'altra app insieme al tuo nome utente." -#: src/view/com/modals/ServerInput.tsx:105 -#~ msgid "Use your domain as your Bluesky client service provider" -#~ msgstr "Utilizza il tuo dominio come provider di servizi clienti Bluesky" - #: src/view/com/modals/InviteCodes.tsx:200 msgid "Used by:" msgstr "Usato da:" @@ -4522,29 +4362,29 @@ msgstr "Handle dell'utente" #: src/view/com/lists/ListCard.tsx:85 #: src/view/com/modals/UserAddRemoveLists.tsx:198 msgid "User list by {0}" -msgstr "Lista utenti di {0}" +msgstr "Lista di {0}" #: src/view/screens/ProfileList.tsx:763 msgid "User list by <0/>" -msgstr "Lista utenti di<0/>" +msgstr "Lista di<0/>" #: src/view/com/lists/ListCard.tsx:83 #: src/view/com/modals/UserAddRemoveLists.tsx:196 #: src/view/screens/ProfileList.tsx:761 msgid "User list by you" -msgstr "La tua lista utenti" +msgstr "La tua lista" #: src/view/com/modals/CreateOrEditList.tsx:196 msgid "User list created" -msgstr "Lista utenti creata" +msgstr "Lista creata" #: src/view/com/modals/CreateOrEditList.tsx:182 msgid "User list updated" -msgstr "Lista utenti aggiornata" +msgstr "Lista aggiornata" #: src/view/screens/Lists.tsx:58 msgid "User Lists" -msgstr "Lista utenti" +msgstr "Liste publiche" #: src/view/com/auth/login/LoginForm.tsx:177 #: src/view/com/auth/login/LoginForm.tsx:195 @@ -4590,7 +4430,7 @@ msgstr "Verifica la tua email" #: src/screens/Onboarding/index.tsx:42 msgid "Video Games" -msgstr "" +msgstr "Video Games" #: src/view/com/profile/ProfileHeader.tsx:662 msgid "View {0}'s avatar" @@ -4623,7 +4463,7 @@ msgstr "Avvisa" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:134 msgid "We also think you'll like \"For You\" by Skygaze:" -msgstr "" +msgstr "Pensiamo che ti piacerà anche \"Per Te\" di Skygaze:" #: src/screens/Hashtag.tsx:132 msgid "We couldn't find any results for that hashtag." @@ -4631,11 +4471,11 @@ msgstr "" #: src/screens/Deactivated.tsx:133 msgid "We estimate {estimatedTime} until your account is ready." -msgstr "" +msgstr "Stimiamo {estimatedTime} prima che il tuo account sia pronto." #: src/screens/Onboarding/StepFinished.tsx:93 msgid "We hope you have a wonderful time. Remember, Bluesky is:" -msgstr "" +msgstr "Speriamo di darti dei momenti dei bei momenti. Ricorda, Bluesky è:" #: src/view/com/posts/DiscoverFallbackHeader.tsx:29 msgid "We ran out of posts from your follows. Here's the latest from <0/>." @@ -4647,15 +4487,15 @@ msgstr "" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124 msgid "We recommend our \"Discover\" feed:" -msgstr "" +msgstr "Consigliamo il nostro feed \"Scopri\":" #: src/screens/Onboarding/StepInterests/index.tsx:133 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 "" +msgstr "Non siamo riusciti a connetterci. Riprova per continuare a configurare il tuo account. Se il problema persiste, puoi ignorare questo flusso." #: src/screens/Deactivated.tsx:137 msgid "We will let you know when your account is ready." -msgstr "" +msgstr "Ti faremo sapere quando il tuo account sarà pronto." #: src/view/com/modals/AppealLabel.tsx:48 msgid "We'll look into your appeal promptly." @@ -4663,7 +4503,7 @@ msgstr "Esamineremo il tuo ricorso al più presto." #: src/screens/Onboarding/StepInterests/index.tsx:138 msgid "We'll use this to help customize your experience." -msgstr "" +msgstr "Lo useremo per personalizzare la tua esperienza." #: src/view/com/auth/create/CreateAccount.tsx:134 msgid "We're so excited to have you join us!" @@ -4692,7 +4532,7 @@ msgstr "Ti diamo il benvenuto a <0>Bluesky</0>" #: src/screens/Onboarding/StepInterests/index.tsx:130 msgid "What are your interests?" -msgstr "" +msgstr "Quali sono i tuoi interessi?" #: src/view/com/modals/report/Modal.tsx:169 msgid "What is the issue with this {collectionName}?" @@ -4734,7 +4574,7 @@ msgstr "Scrivi la tua risposta" #: src/screens/Onboarding/index.tsx:28 msgid "Writers" -msgstr "" +msgstr "Scrittori" #: src/view/com/auth/create/Step2.tsx:263 #~ msgid "XXXXXX" @@ -4752,19 +4592,16 @@ msgstr "Si" #: src/screens/Deactivated.tsx:130 msgid "You are in line." -msgstr "" +msgstr "Sei nella fila." #: src/view/com/posts/FollowingEmptyState.tsx:67 #: src/view/com/posts/FollowingEndOfFeed.tsx:68 msgid "You can also discover new Custom Feeds to follow." msgstr "Puoi anche scoprire nuovi feed personalizzati da seguire." -#~ msgid "You can change hosting providers at any time." -#~ msgstr "Pots canviar el teu proveïdor d'allotjament quan vulguis." - #: src/screens/Onboarding/StepFollowingFeed.tsx:142 msgid "You can change these settings later." -msgstr "" +msgstr "Potrai modificare queste impostazioni in seguito." #: src/view/com/auth/login/Login.tsx:158 #: src/view/com/auth/login/PasswordUpdatedForm.tsx:31 @@ -4800,18 +4637,17 @@ msgstr "Hai bloccato questo utente. Non è possibile visualizzare il contenuto." #: src/view/com/modals/ChangePassword.tsx:87 #: src/view/com/modals/ChangePassword.tsx:121 msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." -msgstr "" +msgstr "Hai inserito un codice non valido. Dovrebbe apparire come XXXX-XXXXXX." #: src/view/com/modals/ModerationDetails.tsx:87 msgid "You have muted this user." -msgstr "Hai silenziato questo utente." +msgstr "Hai disattivato questo utente." #: src/view/com/feeds/ProfileFeedgens.tsx:136 msgid "You have no feeds." msgstr "Non hai feeds." -#: src/view/com/lists/MyLists.tsx:89 -#: src/view/com/lists/ProfileLists.tsx:140 +#: src/view/com/lists/MyLists.tsx:89 src/view/com/lists/ProfileLists.tsx:140 msgid "You have no lists." msgstr "Non hai liste." @@ -4837,7 +4673,7 @@ msgstr "Devi avere almeno 18 anni per abilitare i contenuti per adulti." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:103 msgid "You must be 18 years or older to enable adult content" -msgstr "" +msgstr "Devi avere almeno 18 anni per abilitare i contenuti per adulti" #: src/view/com/util/forms/PostDropdownBtn.tsx:147 msgid "You will no longer receive notifications for this thread" @@ -4853,17 +4689,16 @@ msgstr "Riceverai un'email con un \"codice di reset\". Inserisci il codice qui, #: src/screens/Onboarding/StepModeration/index.tsx:72 msgid "You're in control" -msgstr "" +msgstr "Sei in controllo" -#: src/screens/Deactivated.tsx:87 -#: src/screens/Deactivated.tsx:88 +#: src/screens/Deactivated.tsx:87 src/screens/Deactivated.tsx:88 #: src/screens/Deactivated.tsx:103 msgid "You're in line" -msgstr "" +msgstr "Sei in fila" #: src/screens/Onboarding/StepFinished.tsx:90 msgid "You're ready to go!" -msgstr "" +msgstr "Sei pronto per iniziare!" #: src/view/com/posts/FollowingEndOfFeed.tsx:48 msgid "You've reached the end of your feed! Find some more accounts to follow." @@ -4879,7 +4714,7 @@ msgstr "Il tuo account è stato eliminato" #: src/view/screens/Settings/ExportCarDialog.tsx:47 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 "" +msgstr "L'archivio del tuo account, che contiene tutti i record di dati pubblici, può essere scaricato come file \"CAR\". Questo file non include elementi multimediali incorporati, come immagini o dati privati, che devono essere recuperati separatamente." #: src/view/com/auth/create/Step1.tsx:215 msgid "Your birth date" @@ -4891,7 +4726,7 @@ msgstr "La tua scelta verrà salvata, ma potrà essere modificata successivament #: src/screens/Onboarding/StepFollowingFeed.tsx:61 msgid "Your default feed is \"Following\"" -msgstr "" +msgstr "Il tuo feed predefinito è \"Following\"" #: src/view/com/auth/create/state.ts:110 #: src/view/com/auth/login/ForgotPasswordForm.tsx:70 @@ -4937,7 +4772,7 @@ msgstr "" #: src/view/com/modals/ChangePassword.tsx:155 msgid "Your password has been changed successfully!" -msgstr "" +msgstr "La tua password è stata modificata correttamente!" #: src/view/com/composer/Composer.tsx:274 msgid "Your post has been published" @@ -4961,3 +4796,153 @@ msgstr "La tua risposta è stata pubblicata" #: src/view/com/auth/create/Step2.tsx:65 msgid "Your user handle" msgstr "Il tuo handle utente" + +#~ msgid "{0, plural, one {# invite code available} other {# invite codes available}}" +#~ msgstr "{0, plural, one {# codice d'invito disponibile} other {# codici d'inviti disponibili}}" + +#~ msgid "{0}" +#~ msgstr "{0}" + +#~ msgid "{0} {purposeLabel} List" +#~ msgstr "Lista {purposeLabel} {0}" + +#~ msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" +#~ msgstr "{invitesAvailable, plural, one {Codici d'invito: # available} other {Codici d'invito: # available}}" + +#~ msgid "{invitesAvailable} invite code available" +#~ msgstr "{invitesAvailable} codice d'invito disponibile" + +#~ msgid "{invitesAvailable} invite codes available" +#~ msgstr "{invitesAvailable} codici d'invito disponibili" + +#~ msgid "{message}" +#~ msgstr "{message}" + +#~ msgid "App passwords" +#~ msgstr "Passwords dell'app" + +#~ msgid "Appeal Decision" +#~ msgstr "Decisión de apelación" + +#~ msgid "Bluesky.Social" +#~ msgstr "Bluesky.Social" + +#~ msgid "Button disabled. Input custom domain to proceed." +#~ msgstr "Pulsante disabilitato. Inserisci il dominio personalizzato per procedere." + +#~ msgid "Cancel add image alt text" +#~ msgstr "Cancel·la afegir text a la imatge" + +#~ msgid "Change" +#~ msgstr "Cambia" + +#~ msgid "Danger Zone" +#~ msgstr "Zona di Pericolo" + +#~ msgid "Delete my account…" +#~ msgstr "Cancella il mio account…" + +#~ msgid "Dev Server" +#~ msgstr "Server di sviluppo" + +#~ msgid "Developer Tools" +#~ msgstr "Strumenti per sviluppatori" + +#~ msgid "Discover new feeds" +#~ msgstr "Scopri nuovi feeds" + +#~ msgid "Enter the address of your provider:" +#~ msgstr "Inserisci l'indirizzo del tuo provider:" + +#~ msgid "following" +#~ msgstr "following" + +#~ msgid "Hosting provider address" +#~ msgstr "Indirizzo del fornitore di hosting" + +#~ msgid "Invite" +#~ msgstr "Invita" + +#~ msgid "Invite codes: {invitesAvailable} available" +#~ msgstr "Codici di invito: {invitesAvailable} disponibili" + +#~ msgid "liked your custom feed{0}" +#~ msgstr "piace il feed personalizzato{0}" + +#~ msgid "Local dev server" +#~ msgstr "Server di sviluppo locale" + +#~ msgid "Looks like this feed is only available to users with a Bluesky account. Please sign up or sign in to view this feed!" +#~ msgstr "Sembra che questo feed sia disponibile solo per gli utenti con un account Bluesky. Per favore registrati o accedi per visualizzare questo feed!" + +#~ msgid "Message from server" +#~ msgstr "Messaggio dal server" + +#~ msgid "New Post" +#~ msgstr "Nuovo Post" + +#~ msgid "Opens invite code list" +#~ msgstr "Apre la lista dei codici di invito" + +#~ msgid "Other service" +#~ msgstr "Altro servizio" + +#~ msgid "Please tell us why you think this decision was incorrect." +#~ msgstr "Per favore spiegaci perché ritieni che questa decisione sia stata sbagliata." + +#~ msgid "Post" +#~ msgstr "Post" + +#~ msgid "Quote Post" +#~ msgstr "Cita il post" + +#~ msgid "Reposted by" +#~ msgstr "Repost di" + +#~ msgid "Reposted by {0})" +#~ msgstr "Repost di {0})" + +#~ msgid "Select Bluesky Social" +#~ msgstr "Seleziona Bluesky Social" + +#~ msgid "Send Email" +#~ msgstr "Envia Email" + +#~ msgid "Something went wrong and we're not sure what." +#~ msgstr "Qualcosa è andato storto ma non siamo sicuri di cosa." + +#~ msgid "Staging" +#~ msgstr "Allestimento" + +#~ msgid "Swipe up to see more" +#~ msgstr "Scorri verso l'alto per vedere di più" + +#~ 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 "Il modulo di supporto è stato spostato. Se hai bisogno di aiuto, <0/> o visita {HELP_DESK_URL} per metterti in contatto con noi." + +#~ msgid "This {0} has been labeled." +#~ msgstr "Questo {0} è stato etichettato." + +#~ msgid "This is the service that keeps you online." +#~ msgstr "Questo è il servizio che ti mantiene online." + +#~ msgid "This user is included the <0/> list which you have muted." +#~ msgstr "Questo utente è incluso nella lista <0/> che hai silenziato." + +#~ msgid "Try again" +#~ msgstr "Provalo di nuovo" + +#~ msgid "Use your domain as your Bluesky client service provider" +#~ msgstr "Utilizza il tuo dominio come provider di servizi clienti Bluesky" + +#~ msgid "What's next?" +#~ msgstr "Qual è il prossimo?" + +#~ msgid "You can change hosting providers at any time." +#~ msgstr "Puoi cambiare provider di hosting in qualsiasi momento." + +#~ msgid "Your hosting provider" +#~ msgstr "Il tuo fornitore di hosting" + +#~ msgid "Your invite codes are hidden when logged in using an App Password" +#~ msgstr "I tuoi codici di invito vengono celati quando accedi utilizzando una password per l'app" diff --git a/src/locale/locales/ko/messages.po b/src/locale/locales/ko/messages.po index a4c05239f..28059c3ed 100644 --- a/src/locale/locales/ko/messages.po +++ b/src/locale/locales/ko/messages.po @@ -9,45 +9,27 @@ msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: \n" -"Last-Translator: quiple\n" -"Language-Team: quiple, lens0021, HaruChanHeart, hazzzi\n" +"Last-Translator: heartade\n" +"Language-Team: quiple, lens0021, HaruChanHeart, hazzzi, heartade\n" "Plural-Forms: \n" #: src/view/com/modals/VerifyEmail.tsx:142 msgid "(no email)" msgstr "(ì´ë©”ì¼ ì—†ìŒ)" -#: src/view/shell/desktop/RightNav.tsx:168 -#~ msgid "{0, plural, one {# invite code available} other {# invite codes available}}" -#~ msgstr "{0, plural, one {초대 코드 #ê°œ 사용 가능} other {초대 코드 #ê°œ 사용 가능}}" - -#: src/view/com/profile/ProfileHeader.tsx:593 +#: src/screens/Profile/Header/Metrics.tsx:45 msgid "{following} following" msgstr "{following} 팔로우 중" -#: src/view/shell/desktop/RightNav.tsx:151 -#~ msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" -#~ msgstr "{invitesAvailable, plural, one {초대 코드: #ê°œ 사용 가능} other {초대 코드: #ê°œ 사용 가능}}" - -#: src/view/screens/Settings.tsx:435 -#: src/view/shell/Drawer.tsx:664 -#~ msgid "{invitesAvailable} invite code available" -#~ msgstr "초대 코드 {invitesAvailable}ê°œ 사용 가능" - -#: src/view/screens/Settings.tsx:437 -#: src/view/shell/Drawer.tsx:666 -#~ msgid "{invitesAvailable} invite codes available" -#~ msgstr "초대 코드 {invitesAvailable}ê°œ 사용 가능" - #: src/view/shell/Drawer.tsx:440 msgid "{numUnreadNotifications} unread" msgstr "{numUnreadNotifications}ê°œ ì½ì§€ 않ìŒ" #: src/view/com/threadgate/WhoCanReply.tsx:158 msgid "<0/> members" -msgstr "<0/> 멤버" +msgstr "<0/>ì˜ ë©¤ë²„" -#: src/view/com/profile/ProfileHeader.tsx:595 +#: src/screens/Profile/Header/Metrics.tsx:46 msgid "<0>{following} </0><1>following</1>" msgstr "<0>{following} </0><1>팔로우 중</1>" @@ -63,18 +45,10 @@ msgstr "<1>추천 사용ìž</1><0>팔로우하기</0>" msgid "<0>Welcome to</0><1>Bluesky</1>" msgstr "<1>Bluesky</1><0>ì— ì˜¤ì‹ ê²ƒì„ í™˜ì˜í•©ë‹ˆë‹¤</0>" -#: src/view/com/profile/ProfileHeader.tsx:558 +#: src/screens/Profile/Header/Handle.tsx:42 msgid "âš Invalid Handle" msgstr "âš ìž˜ëª»ëœ í•¸ë“¤" -#: src/view/com/util/moderation/LabelInfo.tsx:45 -msgid "A content warning has been applied to this {0}." -msgstr "ì´ {0}ì— ì½˜í…ì¸ ê²½ê³ ê°€ ì ìš©ë˜ì—ˆìŠµë‹ˆë‹¤." - -#: src/lib/hooks/useOTAUpdate.ts:16 -msgid "A new version of the app is available. Please update to continue using the app." -msgstr "새 ë²„ì „ì˜ ì•±ì„ ì‚¬ìš©í• ìˆ˜ 있습니다. ì•±ì„ ê³„ì† ì‚¬ìš©í•˜ë ¤ë©´ ì—…ë°ì´íŠ¸í•˜ì„¸ìš”." - #: src/view/com/util/ViewHeader.tsx:89 #: src/view/screens/Search/Search.tsx:647 msgid "Access navigation links and settings" @@ -85,29 +59,38 @@ msgid "Access profile and other navigation links" msgstr "프로필 ë° ê¸°íƒ€ íƒìƒ‰ ë§í¬ë¡œ ì´ë™í•©ë‹ˆë‹¤" #: src/view/com/modals/EditImage.tsx:299 -#: src/view/screens/Settings/index.tsx:451 +#: src/view/screens/Settings/index.tsx:469 msgid "Accessibility" msgstr "ì ‘ê·¼ì„±" +#: src/components/moderation/LabelsOnMe.tsx:42 +msgid "account" +msgstr "ê³„ì •" + #: src/view/com/auth/login/LoginForm.tsx:166 -#: src/view/screens/Settings/index.tsx:308 -#: src/view/screens/Settings/index.tsx:721 +#: src/view/screens/Settings/index.tsx:326 +#: src/view/screens/Settings/index.tsx:739 msgid "Account" msgstr "ê³„ì •" -#: src/view/com/profile/ProfileHeader.tsx:246 +#: src/view/com/profile/ProfileMenu.tsx:139 msgid "Account blocked" msgstr "ê³„ì • 차단ë¨" -#: src/view/com/profile/ProfileHeader.tsx:213 +#: src/view/com/profile/ProfileMenu.tsx:153 +msgid "Account followed" +msgstr "ê³„ì • 팔로우함" + +#: src/view/com/profile/ProfileMenu.tsx:113 msgid "Account muted" msgstr "ê³„ì • 뮤트ë¨" -#: src/view/com/modals/ModerationDetails.tsx:86 +#: src/components/moderation/ModerationDetailsDialog.tsx:94 +#: src/lib/moderation/useModerationCauseDescription.ts:91 msgid "Account Muted" msgstr "ê³„ì • 뮤트ë¨" -#: src/view/com/modals/ModerationDetails.tsx:72 +#: src/components/moderation/ModerationDetailsDialog.tsx:83 msgid "Account Muted by List" msgstr "리스트로 ê³„ì • 뮤트ë¨" @@ -119,11 +102,16 @@ msgstr "ê³„ì • 옵션" msgid "Account removed from quick access" msgstr "ë¹ ë¥¸ 액세스ì—서 ê³„ì • ì œê±°" -#: src/view/com/profile/ProfileHeader.tsx:268 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:130 +#: src/view/com/profile/ProfileMenu.tsx:128 msgid "Account unblocked" msgstr "ê³„ì • 차단 í•´ì œë¨" -#: src/view/com/profile/ProfileHeader.tsx:226 +#: src/view/com/profile/ProfileMenu.tsx:166 +msgid "Account unfollowed" +msgstr "ê³„ì • 언팔로우함" + +#: src/view/com/profile/ProfileMenu.tsx:102 msgid "Account unmuted" msgstr "ê³„ì • 언뮤트ë¨" @@ -131,7 +119,7 @@ msgstr "ê³„ì • 언뮤트ë¨" #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:150 #: src/view/com/modals/ListAddRemoveUsers.tsx:264 #: src/view/com/modals/UserAddRemoveLists.tsx:219 -#: src/view/screens/ProfileList.tsx:813 +#: src/view/screens/ProfileList.tsx:827 msgid "Add" msgstr "추가" @@ -139,12 +127,12 @@ msgstr "추가" msgid "Add a content warning" msgstr "콘í…ì¸ ê²½ê³ ì¶”ê°€" -#: src/view/screens/ProfileList.tsx:803 +#: src/view/screens/ProfileList.tsx:817 msgid "Add a user to this list" msgstr "ì´ ë¦¬ìŠ¤íŠ¸ì— ì‚¬ìš©ìž ì¶”ê°€" -#: src/view/screens/Settings/index.tsx:383 -#: src/view/screens/Settings/index.tsx:392 +#: src/view/screens/Settings/index.tsx:401 +#: src/view/screens/Settings/index.tsx:410 msgid "Add account" msgstr "ê³„ì • 추가" @@ -154,47 +142,38 @@ msgstr "ê³„ì • 추가" msgid "Add alt text" msgstr "대체 í…스트 추가하기" -#: src/view/screens/AppPasswords.tsx:102 -#: src/view/screens/AppPasswords.tsx:143 -#: src/view/screens/AppPasswords.tsx:156 +#: src/view/screens/AppPasswords.tsx:104 +#: src/view/screens/AppPasswords.tsx:145 +#: src/view/screens/AppPasswords.tsx:158 msgid "Add App Password" msgstr "앱 비밀번호 추가" -#: src/view/com/modals/report/InputIssueDetails.tsx:41 -#: src/view/com/modals/report/Modal.tsx:191 -msgid "Add details" -msgstr "세부 ì •ë³´ 추가" - -#: src/view/com/modals/report/Modal.tsx:194 -msgid "Add details to report" -msgstr "ì‹ ê³ ì„¸ë¶€ ì •ë³´ 추가" - -#: src/view/com/composer/Composer.tsx:453 +#: src/view/com/composer/Composer.tsx:462 msgid "Add link card" msgstr "ë§í¬ 카드 추가" -#: src/view/com/composer/Composer.tsx:458 +#: src/view/com/composer/Composer.tsx:467 msgid "Add link card:" msgstr "ë§í¬ 카드 추가:" #: src/components/dialogs/MutedWords.tsx:158 msgid "Add mute word for configured settings" -msgstr "" +msgstr "구성 ì„¤ì •ì— ë®¤íŠ¸ 단어 추가" #: src/components/dialogs/MutedWords.tsx:87 msgid "Add muted words and tags" -msgstr "" +msgstr "ë®¤íŠ¸í• ë‹¨ì–´ ë° íƒœê·¸ 추가" #: src/view/com/modals/ChangeHandle.tsx:417 msgid "Add the following DNS record to your domain:" msgstr "ë„ë©”ì¸ì— ë‹¤ìŒ DNS ë ˆì½”ë“œë¥¼ 추가하세요:" -#: src/view/com/profile/ProfileHeader.tsx:310 +#: src/view/com/profile/ProfileMenu.tsx:263 +#: src/view/com/profile/ProfileMenu.tsx:266 msgid "Add to Lists" msgstr "ë¦¬ìŠ¤íŠ¸ì— ì¶”ê°€" -#: src/view/com/feeds/FeedSourceCard.tsx:245 -#: src/view/screens/ProfileFeed.tsx:273 +#: src/view/com/feeds/FeedSourceCard.tsx:234 msgid "Add to my feeds" msgstr "ë‚´ í”¼ë“œì— ì¶”ê°€" @@ -207,34 +186,35 @@ msgstr "추가ë¨" msgid "Added to list" msgstr "ë¦¬ìŠ¤íŠ¸ì— ì¶”ê°€ë¨" -#: src/view/com/feeds/FeedSourceCard.tsx:127 +#: src/view/com/feeds/FeedSourceCard.tsx:108 msgid "Added to my feeds" msgstr "ë‚´ í”¼ë“œì— ì¶”ê°€ë¨" #: src/view/screens/PreferencesFollowingFeed.tsx:173 msgid "Adjust the number of likes a reply must have to be shown in your feed." -msgstr "ë‹µê¸€ì´ í”¼ë“œì— í‘œì‹œë˜ê¸° 위해 필요한 좋아요 표시 수를 ì¡°ì •í•©ë‹ˆë‹¤." +msgstr "ë‹µê¸€ì´ í”¼ë“œì— í‘œì‹œë˜ê¸° 위해 필요한 좋아요 수를 ì¡°ì •í•©ë‹ˆë‹¤." #: src/view/com/modals/SelfLabel.tsx:75 msgid "Adult Content" msgstr "ì„±ì¸ ì½˜í…ì¸ " -#: src/view/com/modals/ContentFilteringSettings.tsx:141 -msgid "Adult content can only be enabled via the Web at <0/>." -msgstr "ì„±ì¸ ì½˜í…ì¸ ëŠ” <0/>ì—서 ì›¹ì„ í†µí•´ì„œë§Œ í™œì„±í™”í• ìˆ˜ 있습니다." +#: src/components/moderation/ModerationLabelPref.tsx:102 +msgid "Adult content is disabled." +msgstr "ì„±ì¸ ì½˜í…ì¸ ê°€ 비활성화ë˜ì–´ 있습니다." -#: src/view/screens/Settings/index.tsx:664 +#: src/screens/Moderation/index.tsx:383 +#: src/view/screens/Settings/index.tsx:682 msgid "Advanced" msgstr "ê³ ê¸‰" #: src/view/screens/Feeds.tsx:666 msgid "All the feeds you've saved, right in one place." -msgstr "" +msgstr "ì €ìž¥í•œ ëª¨ë“ í”¼ë“œë¥¼ 한 ê³³ì—서 확ì¸í•˜ì„¸ìš”." #: src/view/com/auth/login/ForgotPasswordForm.tsx:221 #: src/view/com/modals/ChangePassword.tsx:168 msgid "Already have a code?" -msgstr "" +msgstr "ì´ë¯¸ 코드가 있나요?" #: src/view/com/auth/login/ChooseAccountForm.tsx:98 msgid "Already signed in as @{0}" @@ -260,12 +240,16 @@ msgstr "{0}(으)로 ì´ë©”ì¼ì„ 보냈습니다. ì´ ì´ë©”ì¼ì—는 ì•„ëž˜ì— msgid "An email has been sent to your previous address, {0}. It includes a confirmation code which you can enter below." msgstr "ì´ì „ ì£¼ì†Œì¸ {0}(으)로 ì´ë©”ì¼ì„ 보냈습니다. ì´ ì´ë©”ì¼ì—는 ì•„ëž˜ì— ìž…ë ¥í•˜ëŠ” í™•ì¸ ì½”ë“œê°€ í¬í•¨ë˜ì–´ 있습니다." -#: src/view/com/profile/FollowButton.tsx:30 -#: src/view/com/profile/FollowButton.tsx:40 +#: src/lib/moderation/useReportOptions.ts:26 +msgid "An issue not included in these options" +msgstr "ì–´ë–¤ 옵션ì—ë„ í¬í•¨ë˜ì§€ 않는 ë¬¸ì œ" + +#: src/view/com/profile/FollowButton.tsx:35 +#: src/view/com/profile/FollowButton.tsx:45 msgid "An issue occurred, please try again." msgstr "ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. 다시 시ë„í•´ 주세요." -#: src/view/com/notifications/FeedItem.tsx:237 +#: src/view/com/notifications/FeedItem.tsx:236 #: src/view/com/threadgate/WhoCanReply.tsx:178 msgid "and" msgstr "ë°" @@ -274,11 +258,15 @@ msgstr "ë°" msgid "Animals" msgstr "ë™ë¬¼" +#: src/lib/moderation/useReportOptions.ts:31 +msgid "Anti-Social Behavior" +msgstr "반사회ì 행위" + #: src/view/screens/LanguageSettings.tsx:95 msgid "App Language" msgstr "앱 언어" -#: src/view/screens/AppPasswords.tsx:228 +#: src/view/screens/AppPasswords.tsx:223 msgid "App password deleted" msgstr "앱 비밀번호 ì‚ì œë¨" @@ -290,58 +278,49 @@ msgstr "앱 비밀번호 ì´ë¦„ì—는 문ìž, 숫ìž, 공백, 대시, 밑줄만 msgid "App Password names must be at least 4 characters long." msgstr "앱 비밀번호 ì´ë¦„ì€ 4ìž ì´ìƒì´ì–´ì•¼ 합니다." -#: src/view/screens/Settings/index.tsx:675 +#: src/view/screens/Settings/index.tsx:693 msgid "App password settings" msgstr "앱 비밀번호 ì„¤ì •" -#: src/view/screens/Settings.tsx:650 -#~ msgid "App passwords" -#~ msgstr "앱 비밀번호" - -#: src/Navigation.tsx:239 -#: src/view/screens/AppPasswords.tsx:187 -#: src/view/screens/Settings/index.tsx:684 +#: src/Navigation.tsx:251 +#: src/view/screens/AppPasswords.tsx:189 +#: src/view/screens/Settings/index.tsx:702 msgid "App Passwords" msgstr "앱 비밀번호" -#: src/view/com/util/forms/PostDropdownBtn.tsx:337 -#: src/view/com/util/forms/PostDropdownBtn.tsx:346 -msgid "Appeal content warning" -msgstr "콘í…ì¸ ê²½ê³ ì´ì˜ì‹ ì²" - -#: src/view/com/modals/AppealLabel.tsx:65 -msgid "Appeal Content Warning" -msgstr "콘í…ì¸ ê²½ê³ ì´ì˜ì‹ ì²" +#: src/components/moderation/LabelsOnMeDialog.tsx:134 +#: src/components/moderation/LabelsOnMeDialog.tsx:137 +msgid "Appeal" +msgstr "ì´ì˜ì‹ ì²" -#: src/view/com/util/moderation/LabelInfo.tsx:52 -msgid "Appeal this decision" -msgstr "ì´ ê²°ì •ì— ì´ì˜ì‹ ì²" +#: src/components/moderation/LabelsOnMeDialog.tsx:202 +msgid "Appeal \"{0}\" label" +msgstr "\"{0}\" ë¼ë²¨ ì´ì˜ì‹ ì²" -#: src/view/com/util/moderation/LabelInfo.tsx:56 -msgid "Appeal this decision." -msgstr "ì´ ê²°ì •ì— ì´ì˜ì‹ ì²í•©ë‹ˆë‹¤." +#: src/components/moderation/LabelsOnMeDialog.tsx:193 +msgid "Appeal submitted." +msgstr "ì´ì˜ì‹ ì² ì œì¶œí•¨" -#: src/view/screens/Settings/index.tsx:466 +#: src/view/screens/Settings/index.tsx:484 msgid "Appearance" msgstr "모양" -#: src/view/screens/AppPasswords.tsx:224 +#: src/view/screens/AppPasswords.tsx:265 msgid "Are you sure you want to delete the app password \"{name}\"?" msgstr "앱 비밀번호 \"{name}\"ì„(를) ì‚ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" -#: src/view/com/composer/Composer.tsx:150 +#: src/view/com/feeds/FeedSourceCard.tsx:280 +msgid "Are you sure you want to remove {0} from your feeds?" +msgstr "피드ì—서 {0}ì„(를) ì œê±°í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" + +#: src/view/com/composer/Composer.tsx:504 msgid "Are you sure you'd like to discard this draft?" msgstr "ì´ ì´ˆì•ˆì„ ì‚ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" #: src/components/dialogs/MutedWords.tsx:282 -#: src/view/screens/ProfileList.tsx:365 msgid "Are you sure?" msgstr "ì •ë§ì¸ê°€ìš”?" -#: src/view/com/util/forms/PostDropdownBtn.tsx:322 -msgid "Are you sure? This cannot be undone." -msgstr "ì •ë§ì¸ê°€ìš”? ë˜ëŒë¦´ 수 없습니다." - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:60 msgid "Are you writing in <0>{0}</0>?" msgstr "{0}(으)로 ì“°ê³ ìžˆë‚˜ìš”?" @@ -354,21 +333,22 @@ msgstr "ì˜ˆìˆ " msgid "Artistic or non-erotic nudity." msgstr "ì„ ì •ì ì´ì§€ 않거나 ì˜ˆìˆ ì ì¸ ë…¸ì¶œ." +#: src/components/moderation/LabelsOnMeDialog.tsx:247 +#: src/components/moderation/LabelsOnMeDialog.tsx:248 +#: src/screens/Profile/Header/Shell.tsx:97 #: src/view/com/auth/create/CreateAccount.tsx:158 #: src/view/com/auth/login/ChooseAccountForm.tsx:151 #: src/view/com/auth/login/ForgotPasswordForm.tsx:174 #: src/view/com/auth/login/LoginForm.tsx:259 #: src/view/com/auth/login/SetNewPasswordForm.tsx:179 -#: src/view/com/modals/report/InputIssueDetails.tsx:46 -#: src/view/com/post-thread/PostThread.tsx:472 -#: src/view/com/post-thread/PostThread.tsx:522 -#: src/view/com/post-thread/PostThread.tsx:530 -#: src/view/com/profile/ProfileHeader.tsx:649 +#: src/view/com/post-thread/PostThread.tsx:473 +#: src/view/com/post-thread/PostThread.tsx:523 +#: src/view/com/post-thread/PostThread.tsx:531 #: src/view/com/util/ViewHeader.tsx:87 msgid "Back" msgstr "뒤로" -#: src/view/com/post-thread/PostThread.tsx:480 +#: src/view/com/post-thread/PostThread.tsx:481 msgctxt "action" msgid "Back" msgstr "뒤로" @@ -377,55 +357,61 @@ msgstr "뒤로" msgid "Based on your interest in {interestsText}" msgstr "{interestsText}ì— ëŒ€í•œ 관심사 기반" -#: src/view/screens/Settings/index.tsx:523 +#: src/view/screens/Settings/index.tsx:541 msgid "Basics" msgstr "기본" +#: src/components/dialogs/BirthDateSettings.tsx:101 #: src/view/com/auth/create/Step1.tsx:227 -#: src/view/com/modals/BirthDateSettings.tsx:73 msgid "Birthday" msgstr "ìƒë…„ì›”ì¼" -#: src/view/screens/Settings/index.tsx:340 +#: src/view/screens/Settings/index.tsx:358 msgid "Birthday:" msgstr "ìƒë…„ì›”ì¼:" -#: src/view/com/profile/ProfileHeader.tsx:239 -#: src/view/com/profile/ProfileHeader.tsx:346 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:278 +#: src/view/com/profile/ProfileMenu.tsx:361 +msgid "Block" +msgstr "차단" + +#: src/view/com/profile/ProfileMenu.tsx:300 +#: src/view/com/profile/ProfileMenu.tsx:307 msgid "Block Account" msgstr "ê³„ì • 차단" -#: src/view/screens/ProfileList.tsx:556 +#: src/view/com/profile/ProfileMenu.tsx:344 +msgid "Block Account?" +msgstr "ê³„ì •ì„ ì°¨ë‹¨í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" + +#: src/view/screens/ProfileList.tsx:530 msgid "Block accounts" msgstr "ê³„ì • 차단" -#: src/view/screens/ProfileList.tsx:506 +#: src/view/screens/ProfileList.tsx:478 +#: src/view/screens/ProfileList.tsx:634 msgid "Block list" msgstr "리스트 차단" -#: src/view/screens/ProfileList.tsx:316 +#: src/view/screens/ProfileList.tsx:629 msgid "Block these accounts?" msgstr "ì´ ê³„ì •ë“¤ì„ ì°¨ë‹¨í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" -#: src/view/screens/ProfileList.tsx:320 -msgid "Block this List" -msgstr "ì´ ë¦¬ìŠ¤íŠ¸ 차단" - #: src/view/com/lists/ListCard.tsx:110 -#: src/view/com/util/post-embeds/QuoteEmbed.tsx:61 +#: src/view/com/util/post-embeds/QuoteEmbed.tsx:55 msgid "Blocked" msgstr "차단ë¨" -#: src/view/screens/Moderation.tsx:142 +#: src/screens/Moderation/index.tsx:270 msgid "Blocked accounts" msgstr "차단한 ê³„ì •" -#: src/Navigation.tsx:132 +#: src/Navigation.tsx:134 #: src/view/screens/ModerationBlockedAccounts.tsx:107 msgid "Blocked Accounts" msgstr "차단한 ê³„ì •" -#: src/view/com/profile/ProfileHeader.tsx:241 +#: src/view/com/profile/ProfileMenu.tsx:356 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." msgstr "차단한 ê³„ì •ì€ ë‚´ ìŠ¤ë ˆë“œì— ë‹µê¸€ì„ ë‹¬ê±°ë‚˜ 나를 멘션하거나 기타 다른 ë°©ì‹ìœ¼ë¡œ 나와 ìƒí˜¸ìž‘ìš©í• ìˆ˜ 없습니다." @@ -433,14 +419,22 @@ 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:324 +#: src/view/com/post-thread/PostThread.tsx:325 msgid "Blocked post." msgstr "ì°¨ë‹¨ëœ ê²Œì‹œë¬¼." -#: src/view/screens/ProfileList.tsx:318 +#: src/screens/Profile/Sections/Labels.tsx:171 +msgid "Blocking does not prevent this labeler from placing labels on your account." +msgstr "차단하ë”ë¼ë„ ì´ ë¼ë²¨ëŸ¬ê°€ ë‚´ ê³„ì •ì— ë¼ë²¨ì„ ë¶™ì´ëŠ” ê²ƒì„ ë§‰ì§€ëŠ” 못합니다." + +#: src/view/screens/ProfileList.tsx:631 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:353 +msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you." +msgstr "차단하ë”ë¼ë„ ë‚´ ê³„ì •ì— ë¼ë²¨ì´ 붙는 ê²ƒì€ ë§‰ì§€ 못하지만, ì´ ê³„ì •ì´ ë‚´ ìŠ¤ë ˆë“œì— ë‹µê¸€ì„ ë‹¬ê±°ë‚˜ 나와 ìƒí˜¸ìž‘용하는 ê²ƒì€ ì¤‘ì§€ë©ë‹ˆë‹¤." + #: src/view/com/auth/HomeLoggedOutCTA.tsx:93 #: src/view/com/auth/SplashScreen.web.tsx:133 msgid "Blog" @@ -454,7 +448,7 @@ msgstr "Bluesky" #: src/view/com/auth/server-input/index.tsx:150 msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers." -msgstr "" +msgstr "Bluesky는 호스팅 ì œê³µìžë¥¼ ì„ íƒí• 수 있는 개방형 네트워í¬ìž…니다. 개발ìžë¥¼ 위한 ì‚¬ìš©ìž ì§€ì • í˜¸ìŠ¤íŒ…ì´ ë² íƒ€ ë²„ì „ìœ¼ë¡œ ì œê³µë©ë‹ˆë‹¤." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:80 @@ -471,23 +465,23 @@ msgstr "Bluesky는 ì—´ë ¤ 있습니다." msgid "Bluesky is public." msgstr "Bluesky는 공개ì 입니다." -#: src/view/com/modals/Waitlist.tsx:70 -#~ msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon." -#~ msgstr "Bluesky는 ë” ê±´ê°•í•œ 커뮤니티를 구축하기 위해 초대 ë°©ì‹ì„ 사용합니다. 초대해 준 ì‚¬ëžŒì´ ì—†ëŠ” 경우 ëŒ€ê¸°ìž ëª…ë‹¨ì— ë“±ë¡í•˜ë©´ ê³§ 초대를 ë³´ë‚´ê² ìŠµë‹ˆë‹¤." - -#: src/view/screens/Moderation.tsx:245 +#: src/screens/Moderation/index.tsx:539 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 "로그아웃한 사용ìžì—게 ë‚´ 프로필과 ê²Œì‹œë¬¼ì„ í‘œì‹œí•˜ì§€ 않습니다. 다른 앱ì—서는 ì´ ì„¤ì •ì„ ë”°ë¥´ì§€ ì•Šì„ ìˆ˜ 있습니다. ë‚´ ê³„ì •ì„ ë¹„ê³µê°œë¡œ ì „í™˜í•˜ì§€ëŠ” 않습니다." -#: src/view/com/modals/ServerInput.tsx:78 -#~ msgid "Bluesky.Social" -#~ msgstr "Bluesky.Social" +#: 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:33 msgid "Books" msgstr "ì±…" -#: src/view/screens/Settings/index.tsx:859 +#: src/view/screens/Settings/index.tsx:887 msgid "Build version {0} {1}" msgstr "빌드 ë²„ì „ {0} {1}" @@ -496,18 +490,18 @@ msgstr "빌드 ë²„ì „ {0} {1}" msgid "Business" msgstr "비즈니스" -#: src/view/com/modals/ServerInput.tsx:115 -#~ msgid "Button disabled. Input custom domain to proceed." -#~ msgstr "ë²„íŠ¼ì´ ë¹„í™œì„±í™”ë˜ì—ˆìŠµë‹ˆë‹¤. 계ì†í•˜ë ¤ë©´ ì‚¬ìš©ìž ì§€ì • ë„ë©”ì¸ì„ ìž…ë ¥í•˜ì„¸ìš”" - #: src/view/com/profile/ProfileSubpageHeader.tsx:157 msgid "by —" -msgstr "—" +msgstr "— ë‹˜ì´ ë§Œë“¦" #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:100 msgid "by {0}" msgstr "{0} ë‹˜ì´ ë§Œë“¦" +#: src/components/LabelingServiceCard/index.tsx:57 +msgid "By {0}" +msgstr "{0} ë‹˜ì´ ë§Œë“¦" + #: src/view/com/profile/ProfileSubpageHeader.tsx:161 msgid "by <0/>" msgstr "<0/> ë‹˜ì´ ë§Œë“¦" @@ -516,9 +510,7 @@ msgstr "<0/> ë‹˜ì´ ë§Œë“¦" msgid "by you" msgstr "ë‚´ê°€ 만듦" -#: src/view/com/composer/photos/OpenCameraBtn.tsx:60 -#: src/view/com/util/UserAvatar.tsx:224 -#: src/view/com/util/UserBanner.tsx:40 +#: src/view/com/composer/photos/OpenCameraBtn.tsx:77 msgid "Camera" msgstr "ì¹´ë©”ë¼" @@ -526,9 +518,10 @@ msgstr "ì¹´ë©”ë¼" 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/Prompt.tsx:101 -#: src/view/com/composer/Composer.tsx:307 -#: src/view/com/composer/Composer.tsx:312 +#: src/components/Prompt.tsx:116 +#: src/components/Prompt.tsx:118 +#: src/view/com/composer/Composer.tsx:316 +#: src/view/com/composer/Composer.tsx:321 #: src/view/com/modals/ChangeEmail.tsx:218 #: src/view/com/modals/ChangeEmail.tsx:220 #: src/view/com/modals/ChangePassword.tsx:265 @@ -546,8 +539,6 @@ msgstr "글ìž, 숫ìž, 공백, 대시, 밑줄만 í¬í•¨í• 수 있습니다. ê¸ msgid "Cancel" msgstr "취소" -#: src/view/com/modals/Confirm.tsx:88 -#: src/view/com/modals/Confirm.tsx:91 #: src/view/com/modals/CreateOrEditList.tsx:360 #: src/view/com/modals/DeleteAccount.tsx:156 #: src/view/com/modals/DeleteAccount.tsx:234 @@ -581,21 +572,17 @@ msgstr "게시물 ì¸ìš© 취소" msgid "Cancel search" msgstr "검색 취소" -#: src/view/com/modals/Waitlist.tsx:136 -#~ msgid "Cancel waitlist signup" -#~ msgstr "ëŒ€ê¸°ìž ëª…ë‹¨ ë“±ë¡ ì·¨ì†Œ" - -#: src/view/screens/Settings/index.tsx:334 +#: src/view/screens/Settings/index.tsx:352 msgctxt "action" msgid "Change" msgstr "변경" -#: src/view/screens/Settings/index.tsx:696 +#: src/view/screens/Settings/index.tsx:714 msgid "Change handle" msgstr "핸들 변경" #: src/view/com/modals/ChangeHandle.tsx:161 -#: src/view/screens/Settings/index.tsx:705 +#: src/view/screens/Settings/index.tsx:723 msgid "Change Handle" msgstr "핸들 변경" @@ -603,21 +590,21 @@ msgstr "핸들 변경" msgid "Change my email" msgstr "ë‚´ ì´ë©”ì¼ ë³€ê²½í•˜ê¸°" -#: src/view/screens/Settings/index.tsx:732 +#: src/view/screens/Settings/index.tsx:750 msgid "Change password" -msgstr "" +msgstr "비밀번호 변경" -#: src/view/screens/Settings/index.tsx:741 +#: src/view/screens/Settings/index.tsx:759 msgid "Change Password" -msgstr "" +msgstr "비밀번호 변경" #: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 msgid "Change post language to {0}" msgstr "게시물 언어를 {0}(으)로 변경" -#: src/view/screens/Settings/index.tsx:733 +#: src/view/screens/Settings/index.tsx:751 msgid "Change your Bluesky password" -msgstr "" +msgstr "ë‚´ Bluesky 비밀번호를 변경합니다" #: src/view/com/modals/ChangeEmail.tsx:109 msgid "Change Your Email" @@ -644,7 +631,7 @@ msgstr "ë°›ì€ íŽ¸ì§€í•¨ì—서 ì•„ëž˜ì— ìž…ë ¥í•˜ëŠ” í™•ì¸ ì½”ë“œê°€ í¬í•¨ëœ msgid "Choose \"Everybody\" or \"Nobody\"" msgstr "\"모ë‘\" ë˜ëŠ” \"ì—†ìŒ\"ì„ ì„ íƒí•˜ì„¸ìš”." -#: src/view/screens/Settings/index.tsx:697 +#: src/view/screens/Settings/index.tsx:715 msgid "Choose a new Bluesky username or create" msgstr "새 Bluesky ì‚¬ìš©ìž ì´ë¦„ì„ ì„ íƒí•˜ê±°ë‚˜ ë§Œë“니다" @@ -659,7 +646,7 @@ msgstr "맞춤 피드를 구ë™í• ì•Œê³ ë¦¬ì¦˜ì„ ì„ íƒí•˜ì„¸ìš”." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:83 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:83 msgid "Choose the algorithms that power your experience with custom feeds." -msgstr "맞춤 피드를 통해 ì‚¬ìš©ìž ê²½í—˜ì„ ê°•í™”í•˜ëŠ” ì•Œê³ ë¦¬ì¦˜ì„ ì„ íƒí•©ë‹ˆë‹¤." +msgstr "맞춤 피드를 통해 ì‚¬ìš©ìž ê²½í—˜ì„ ê°•í™”í•˜ëŠ” ì•Œê³ ë¦¬ì¦˜ì„ ì„ íƒí•˜ì„¸ìš”." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 msgid "Choose your main feeds" @@ -669,21 +656,21 @@ msgstr "기본 피드 ì„ íƒ" msgid "Choose your password" msgstr "비밀번호를 ìž…ë ¥í•˜ì„¸ìš”" -#: src/view/screens/Settings/index.tsx:834 -#: src/view/screens/Settings/index.tsx:835 +#: src/view/screens/Settings/index.tsx:862 +#: src/view/screens/Settings/index.tsx:863 msgid "Clear all legacy storage data" msgstr "ëª¨ë“ ë ˆê±°ì‹œ ìŠ¤í† ë¦¬ì§€ ë°ì´í„° 지우기" -#: src/view/screens/Settings/index.tsx:837 +#: src/view/screens/Settings/index.tsx:865 msgid "Clear all legacy storage data (restart after this)" msgstr "ëª¨ë“ ë ˆê±°ì‹œ ìŠ¤í† ë¦¬ì§€ ë°ì´í„° 지우기 (ì´í›„ 다시 시작)" -#: src/view/screens/Settings/index.tsx:846 -#: src/view/screens/Settings/index.tsx:847 +#: src/view/screens/Settings/index.tsx:874 +#: src/view/screens/Settings/index.tsx:875 msgid "Clear all storage data" msgstr "ëª¨ë“ ìŠ¤í† ë¦¬ì§€ ë°ì´í„° 지우기" -#: src/view/screens/Settings/index.tsx:849 +#: src/view/screens/Settings/index.tsx:877 msgid "Clear all storage data (restart after this)" msgstr "ëª¨ë“ ìŠ¤í† ë¦¬ì§€ ë°ì´í„° 지우기 (ì´í›„ 다시 시작)" @@ -698,11 +685,11 @@ msgstr "ì´ê³³ì„ í´ë¦" #: src/components/TagMenu/index.web.tsx:138 msgid "Click here to open tag menu for {tag}" -msgstr "" +msgstr "ì´ê³³ì„ í´ë¦í•˜ì—¬ {tag}ì˜ íƒœê·¸ 메뉴 열기" #: src/components/RichText.tsx:191 msgid "Click here to open tag menu for #{tag}" -msgstr "" +msgstr "ì´ê³³ì„ í´ë¦í•˜ì—¬ #{tag}ì˜ íƒœê·¸ 메뉴 열기" #: src/screens/Onboarding/index.tsx:35 msgid "Climate" @@ -711,22 +698,22 @@ msgstr "기후" #: src/view/com/modals/ChangePassword.tsx:265 #: src/view/com/modals/ChangePassword.tsx:268 msgid "Close" -msgstr "" +msgstr "닫기" #: src/components/Dialog/index.web.tsx:84 #: src/components/Dialog/index.web.tsx:198 msgid "Close active dialog" -msgstr "활성 대화 ìƒìž 닫기" +msgstr "ì—´ë ¤ 있는 대화 ìƒìž 닫기" #: src/view/com/auth/login/PasswordUpdatedForm.tsx:38 msgid "Close alert" msgstr "알림 닫기" -#: src/view/com/util/BottomSheetCustomBackdrop.tsx:33 +#: src/view/com/util/BottomSheetCustomBackdrop.tsx:36 msgid "Close bottom drawer" msgstr "하단 ì„œëž ë‹«ê¸°" -#: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:26 +#: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:30 msgid "Close image" msgstr "ì´ë¯¸ì§€ 닫기" @@ -734,15 +721,16 @@ msgstr "ì´ë¯¸ì§€ 닫기" msgid "Close image viewer" msgstr "ì´ë¯¸ì§€ ë·°ì–´ 닫기" -#: src/view/shell/index.web.tsx:51 +#: src/view/shell/index.web.tsx:55 msgid "Close navigation footer" msgstr "íƒìƒ‰ 푸터 닫기" +#: src/components/Menu/index.tsx:207 #: src/components/TagMenu/index.tsx:262 msgid "Close this dialog" -msgstr "" +msgstr "ì´ ëŒ€í™” ìƒìž 닫기" -#: src/view/shell/index.web.tsx:52 +#: src/view/shell/index.web.tsx:56 msgid "Closes bottom navigation bar" msgstr "하단 íƒìƒ‰ 막대를 닫습니다" @@ -750,15 +738,15 @@ msgstr "하단 íƒìƒ‰ 막대를 닫습니다" msgid "Closes password update alert" msgstr "비밀번호 변경 ì•Œë¦¼ì„ ë‹«ìŠµë‹ˆë‹¤" -#: src/view/com/composer/Composer.tsx:309 +#: src/view/com/composer/Composer.tsx:318 msgid "Closes post composer and discards post draft" msgstr "게시물 작성 ìƒìžë¥¼ ë‹«ê³ ê²Œì‹œë¬¼ ì´ˆì•ˆì„ ì‚ì œí•©ë‹ˆë‹¤" -#: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:27 +#: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:31 msgid "Closes viewer for header image" msgstr "í—¤ë” ì´ë¯¸ì§€ 뷰어를 닫습니다" -#: src/view/com/notifications/FeedItem.tsx:318 +#: src/view/com/notifications/FeedItem.tsx:317 msgid "Collapses list of users for a given notification" msgstr "ì´ ì•Œë¦¼ì— ëŒ€í•œ ì‚¬ìš©ìž ëª©ë¡ì„ 축소합니다" @@ -770,7 +758,7 @@ msgstr "코미디" msgid "Comics" msgstr "만화" -#: src/Navigation.tsx:229 +#: src/Navigation.tsx:241 #: src/view/screens/CommunityGuidelines.tsx:32 msgid "Community Guidelines" msgstr "커뮤니티 ê°€ì´ë“œë¼ì¸" @@ -781,9 +769,9 @@ msgstr "온보딩 완료 후 ê³„ì • 사용 시작" #: src/view/com/auth/create/Step3.tsx:73 msgid "Complete the challenge" -msgstr "" +msgstr "챌린지 완료하기" -#: src/view/com/composer/Composer.tsx:424 +#: src/view/com/composer/Composer.tsx:433 msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" msgstr "최대 {MAX_GRAPHEME_LENGTH}ìž ê¸¸ì´ê¹Œì§€ ê¸€ì„ ìž‘ì„±í• ìˆ˜ 있습니다" @@ -791,12 +779,18 @@ msgstr "최대 {MAX_GRAPHEME_LENGTH}ìž ê¸¸ì´ê¹Œì§€ ê¸€ì„ ìž‘ì„±í• ìˆ˜ 있습 msgid "Compose reply" msgstr "답글 작성하기" -#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:67 +#: src/components/moderation/GlobalModerationLabelPref.tsx:69 +#: src/components/moderation/ModerationLabelPref.tsx:128 +#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:81 msgid "Configure content filtering setting for category: {0}" msgstr "{0} ì¹´í…Œê³ ë¦¬ì— ëŒ€í•œ 콘í…ì¸ í•„í„°ë§ ì„¤ì • 구성" -#: src/components/Prompt.tsx:124 -#: src/view/com/modals/AppealLabel.tsx:98 +#: src/components/moderation/ModerationLabelPref.tsx:104 +msgid "Configured in <0>moderation settings</0>." +msgstr "<0>ê²€í† ì„¤ì •</0>ì—서 ì„¤ì •í•©ë‹ˆë‹¤." + +#: src/components/Prompt.tsx:152 +#: src/components/Prompt.tsx:155 #: src/view/com/modals/SelfLabel.tsx:154 #: src/view/com/modals/VerifyEmail.tsx:231 #: src/view/com/modals/VerifyEmail.tsx:233 @@ -805,12 +799,6 @@ msgstr "{0} ì¹´í…Œê³ ë¦¬ì— ëŒ€í•œ 콘í…ì¸ í•„í„°ë§ ì„¤ì • 구성" msgid "Confirm" msgstr "확ì¸" -#: src/view/com/modals/Confirm.tsx:75 -#: src/view/com/modals/Confirm.tsx:78 -msgctxt "action" -msgid "Confirm" -msgstr "확ì¸" - #: src/view/com/modals/ChangeEmail.tsx:193 #: src/view/com/modals/ChangeEmail.tsx:195 msgid "Confirm Change" @@ -824,9 +812,13 @@ msgstr "콘í…ì¸ ì–¸ì–´ ì„¤ì • 확ì¸" msgid "Confirm delete account" msgstr "ê³„ì • ì‚ì œ 확ì¸" -#: src/view/com/modals/ContentFilteringSettings.tsx:156 -msgid "Confirm your age to enable adult content." -msgstr "ì„±ì¸ ì½˜í…ì¸ ë¥¼ ì‚¬ìš©í•˜ë ¤ë©´ 나ì´ë¥¼ 확ì¸í•˜ì„¸ìš”." +#: src/screens/Moderation/index.tsx:304 +msgid "Confirm your age:" +msgstr "나ì´ë¥¼ 확ì¸í•˜ì„¸ìš”:" + +#: src/screens/Moderation/index.tsx:295 +msgid "Confirm your birthdate" +msgstr "ìƒë…„ì›”ì¼ í™•ì¸" #: src/view/com/modals/ChangeEmail.tsx:157 #: src/view/com/modals/DeleteAccount.tsx:182 @@ -834,10 +826,6 @@ msgstr "ì„±ì¸ ì½˜í…ì¸ ë¥¼ ì‚¬ìš©í•˜ë ¤ë©´ 나ì´ë¥¼ 확ì¸í•˜ì„¸ìš”." msgid "Confirmation code" msgstr "í™•ì¸ ì½”ë“œ" -#: src/view/com/modals/Waitlist.tsx:120 -#~ msgid "Confirms signing up {email} to the waitlist" -#~ msgstr "{email}ì„(를) ëŒ€ê¸°ìž ëª…ë‹¨ì— ë“±ë¡í•©ë‹ˆë‹¤" - #: src/view/com/auth/create/CreateAccount.tsx:193 #: src/view/com/auth/login/LoginForm.tsx:278 msgid "Connecting..." @@ -847,25 +835,32 @@ msgstr "ì—°ê²° 중…" msgid "Contact support" msgstr "ì§€ì›ì— ì—°ë½í•˜ê¸°" -#: src/view/screens/Moderation.tsx:83 -msgid "Content filtering" -msgstr "콘í…ì¸ í•„í„°ë§" +#: src/components/moderation/LabelsOnMe.tsx:42 +msgid "content" +msgstr "콘í…ì¸ " + +#: src/lib/moderation/useGlobalLabelStrings.ts:18 +msgid "Content Blocked" +msgstr "콘í…ì¸ ì°¨ë‹¨ë¨" -#: src/view/com/modals/ContentFilteringSettings.tsx:44 -msgid "Content Filtering" -msgstr "콘í…ì¸ í•„í„°ë§" +#: src/screens/Moderation/index.tsx:288 +msgid "Content filters" +msgstr "콘í…ì¸ í•„í„°" #: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:74 #: src/view/screens/LanguageSettings.tsx:278 msgid "Content Languages" msgstr "콘í…ì¸ ì–¸ì–´" -#: src/view/com/modals/ModerationDetails.tsx:65 +#: src/components/moderation/ModerationDetailsDialog.tsx:76 +#: src/lib/moderation/useModerationCauseDescription.ts:75 msgid "Content Not Available" msgstr "콘í…ì¸ ë¥¼ ì‚¬ìš©í• ìˆ˜ ì—†ìŒ" -#: src/view/com/modals/ModerationDetails.tsx:33 -#: src/view/com/util/moderation/ScreenHider.tsx:78 +#: src/components/moderation/ModerationDetailsDialog.tsx:47 +#: src/components/moderation/ScreenHider.tsx:99 +#: src/lib/moderation/useGlobalLabelStrings.ts:22 +#: src/lib/moderation/useModerationCauseDescription.ts:38 msgid "Content Warning" msgstr "콘í…ì¸ ê²½ê³ " @@ -873,10 +868,14 @@ msgstr "콘í…ì¸ ê²½ê³ " msgid "Content warnings" msgstr "콘í…ì¸ ê²½ê³ " +#: src/components/Menu/index.web.tsx:84 +msgid "Context menu backdrop, click to close the menu." +msgstr "컨í…스트 메뉴 ë°°ê²½ì„ í´ë¦í•˜ì—¬ 메뉴를 닫습니다." + #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:170 #: src/screens/Onboarding/StepFollowingFeed.tsx:153 #: src/screens/Onboarding/StepInterests/index.tsx:248 -#: src/screens/Onboarding/StepModeration/index.tsx:118 +#: src/screens/Onboarding/StepModeration/index.tsx:102 #: src/screens/Onboarding/StepTopicalFeeds.tsx:114 #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:148 #: src/view/com/auth/onboarding/RecommendedFollows.tsx:209 @@ -885,7 +884,7 @@ msgstr "계ì†" #: src/screens/Onboarding/StepFollowingFeed.tsx:150 #: src/screens/Onboarding/StepInterests/index.tsx:245 -#: src/screens/Onboarding/StepModeration/index.tsx:115 +#: src/screens/Onboarding/StepModeration/index.tsx:99 #: src/screens/Onboarding/StepTopicalFeeds.tsx:111 msgid "Continue to next step" msgstr "ë‹¤ìŒ ë‹¨ê³„ë¡œ 계ì†í•˜ê¸°" @@ -907,13 +906,13 @@ msgstr "요리" msgid "Copied" msgstr "복사ë¨" -#: src/view/screens/Settings/index.tsx:241 +#: src/view/screens/Settings/index.tsx:247 msgid "Copied build version to clipboard" msgstr "빌드 ë²„ì „ í´ë¦½ë³´ë“œì— 복사ë¨" #: src/view/com/modals/AddAppPasswords.tsx:76 #: src/view/com/modals/InviteCodes.tsx:152 -#: src/view/com/util/forms/PostDropdownBtn.tsx:161 +#: src/view/com/util/forms/PostDropdownBtn.tsx:158 msgid "Copied to clipboard" msgstr "í´ë¦½ë³´ë“œì— 복사ë¨" @@ -925,48 +924,40 @@ msgstr "앱 비밀번호를 복사합니다" msgid "Copy" msgstr "복사" -#: src/view/screens/ProfileList.tsx:418 +#: src/view/screens/ProfileList.tsx:388 msgid "Copy link to list" msgstr "리스트 ë§í¬ 복사" -#: src/view/com/util/forms/PostDropdownBtn.tsx:231 +#: src/view/com/util/forms/PostDropdownBtn.tsx:228 #: src/view/com/util/forms/PostDropdownBtn.tsx:237 msgid "Copy link to post" msgstr "게시물 ë§í¬ 복사" -#: src/view/com/profile/ProfileHeader.tsx:295 -msgid "Copy link to profile" -msgstr "프로필 ë§í¬ 복사" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:223 -#: src/view/com/util/forms/PostDropdownBtn.tsx:225 +#: src/view/com/util/forms/PostDropdownBtn.tsx:220 +#: src/view/com/util/forms/PostDropdownBtn.tsx:222 msgid "Copy post text" msgstr "게시물 í…스트 복사" -#: src/Navigation.tsx:234 +#: src/Navigation.tsx:246 #: src/view/screens/CopyrightPolicy.tsx:29 msgid "Copyright Policy" msgstr "ì €ìž‘ê¶Œ ì •ì±…" -#: src/view/screens/ProfileFeed.tsx:97 +#: src/view/screens/ProfileFeed.tsx:102 msgid "Could not load feed" msgstr "피드를 불러올 수 없습니다" -#: src/view/screens/ProfileList.tsx:893 +#: src/view/screens/ProfileList.tsx:907 msgid "Could not load list" msgstr "리스트를 불러올 수 없습니다" -#: src/view/com/auth/create/Step2.tsx:91 -#~ msgid "Country" -#~ msgstr "êµê°€" - #: src/view/com/auth/HomeLoggedOutCTA.tsx:62 #: src/view/com/auth/SplashScreen.tsx:71 #: src/view/com/auth/SplashScreen.web.tsx:81 msgid "Create a new account" msgstr "새 ê³„ì • 만들기" -#: src/view/screens/Settings/index.tsx:384 +#: src/view/screens/Settings/index.tsx:402 msgid "Create a new Bluesky account" msgstr "새 Bluesky ê³„ì •ì„ ë§Œë“니다" @@ -983,19 +974,15 @@ msgstr "앱 비밀번호 만들기" msgid "Create new account" msgstr "새 ê³„ì • 만들기" -#: src/view/screens/AppPasswords.tsx:249 -msgid "Created {0}" -msgstr "{0} ìƒì„±ë¨" +#: src/components/ReportDialog/SelectReportOptionView.tsx:93 +msgid "Create report for {0}" +msgstr "{0}ì— ëŒ€í•œ ì‹ ê³ ìž‘ì„±í•˜ê¸°" -#: src/view/screens/ProfileFeed.tsx:616 -msgid "Created by <0/>" -msgstr "<0/> ë‹˜ì´ ë§Œë“¦" - -#: src/view/screens/ProfileFeed.tsx:614 -msgid "Created by you" -msgstr "ë‚´ê°€ 만듦" +#: src/view/screens/AppPasswords.tsx:246 +msgid "Created {0}" +msgstr "{0}ì— ìƒì„±ë¨" -#: src/view/com/composer/Composer.tsx:455 +#: src/view/com/composer/Composer.tsx:464 msgid "Creates a card with a thumbnail. The card links to {url}" msgstr "미리보기 ì´ë¯¸ì§€ê°€ 있는 카드를 ë§Œë“니다. 카드가 {url}(으)로 ì—°ê²°ë©ë‹ˆë‹¤" @@ -1006,7 +993,7 @@ msgstr "문화" #: src/view/com/auth/server-input/index.tsx:95 #: src/view/com/auth/server-input/index.tsx:96 msgid "Custom" -msgstr "" +msgstr "ì‚¬ìš©ìž ì§€ì •" #: src/view/com/modals/ChangeHandle.tsx:389 msgid "Custom domain" @@ -1021,12 +1008,8 @@ msgstr "커뮤니티ì—서 구축한 맞춤 피드는 새로운 ê²½í—˜ì„ ì œê³µ msgid "Customize media from external sites." msgstr "외부 사ì´íЏ 미디어를 ì‚¬ìš©ìž ì§€ì •í•©ë‹ˆë‹¤." -#: src/view/screens/Settings.tsx:687 -#~ msgid "Danger Zone" -#~ msgstr "위험 구ì—" - -#: src/view/screens/Settings/index.tsx:485 -#: src/view/screens/Settings/index.tsx:511 +#: src/view/screens/Settings/index.tsx:503 +#: src/view/screens/Settings/index.tsx:529 msgid "Dark" msgstr "ì–´ë‘움" @@ -1034,15 +1017,25 @@ msgstr "ì–´ë‘움" msgid "Dark mode" msgstr "ì–´ë‘ìš´ 모드" -#: src/view/screens/Settings/index.tsx:498 +#: src/view/screens/Settings/index.tsx:516 msgid "Dark Theme" -msgstr "" +msgstr "ì–´ë‘ìš´ 테마" + +#: src/view/screens/Settings/index.tsx:835 +msgid "Debug Moderation" +msgstr "ê²€í† ë””ë²„ê·¸" #: src/view/screens/Debug.tsx:83 msgid "Debug panel" msgstr "디버그 패ë„" -#: src/view/screens/Settings/index.tsx:772 +#: src/view/com/util/forms/PostDropdownBtn.tsx:319 +#: src/view/screens/AppPasswords.tsx:268 +#: src/view/screens/ProfileList.tsx:613 +msgid "Delete" +msgstr "ì‚ì œ" + +#: src/view/screens/Settings/index.tsx:790 msgid "Delete account" msgstr "ê³„ì • ì‚ì œ" @@ -1050,13 +1043,15 @@ msgstr "ê³„ì • ì‚ì œ" msgid "Delete Account" msgstr "ê³„ì • ì‚ì œ" -#: src/view/screens/AppPasswords.tsx:222 -#: src/view/screens/AppPasswords.tsx:242 +#: src/view/screens/AppPasswords.tsx:239 msgid "Delete app password" msgstr "앱 비밀번호 ì‚ì œ" -#: src/view/screens/ProfileList.tsx:364 -#: src/view/screens/ProfileList.tsx:445 +#: src/view/screens/AppPasswords.tsx:263 +msgid "Delete app password?" +msgstr "앱 비밀번호를 ì‚ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" + +#: src/view/screens/ProfileList.tsx:415 msgid "Delete List" msgstr "리스트 ì‚ì œ" @@ -1064,28 +1059,28 @@ msgstr "리스트 ì‚ì œ" msgid "Delete my account" msgstr "ë‚´ ê³„ì • ì‚ì œ" -#: src/view/screens/Settings.tsx:706 -#~ msgid "Delete my account…" -#~ msgstr "ë‚´ ê³„ì • ì‚ì œâ€¦" - -#: src/view/screens/Settings/index.tsx:784 +#: src/view/screens/Settings/index.tsx:802 msgid "Delete My Account…" -msgstr "" +msgstr "ë‚´ ê³„ì • ì‚ì œâ€¦" -#: src/view/com/util/forms/PostDropdownBtn.tsx:317 -#: src/view/com/util/forms/PostDropdownBtn.tsx:326 +#: src/view/com/util/forms/PostDropdownBtn.tsx:302 +#: src/view/com/util/forms/PostDropdownBtn.tsx:304 msgid "Delete post" msgstr "게시물 ì‚ì œ" -#: src/view/com/util/forms/PostDropdownBtn.tsx:321 +#: src/view/screens/ProfileList.tsx:608 +msgid "Delete this list?" +msgstr "ì´ ë¦¬ìŠ¤íŠ¸ë¥¼ ì‚ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" + +#: src/view/com/util/forms/PostDropdownBtn.tsx:314 msgid "Delete this post?" msgstr "ì´ ê²Œì‹œë¬¼ì„ ì‚ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" -#: src/view/com/util/post-embeds/QuoteEmbed.tsx:70 +#: src/view/com/util/post-embeds/QuoteEmbed.tsx:64 msgid "Deleted" msgstr "ì‚ì œë¨" -#: src/view/com/post-thread/PostThread.tsx:316 +#: src/view/com/post-thread/PostThread.tsx:317 msgid "Deleted post." msgstr "ì‚ì œëœ ê²Œì‹œë¬¼." @@ -1096,27 +1091,31 @@ msgstr "ì‚ì œëœ ê²Œì‹œë¬¼." msgid "Description" msgstr "설명" -#: src/view/screens/Settings.tsx:760 -#~ msgid "Developer Tools" -#~ msgstr "ê°œë°œìž ë„구" - -#: src/view/com/composer/Composer.tsx:218 +#: src/view/com/composer/Composer.tsx:217 msgid "Did you want to say anything?" msgstr "í•˜ê³ ì‹¶ì€ ë§ì´ 있나요?" -#: src/view/screens/Settings/index.tsx:504 +#: src/view/screens/Settings/index.tsx:522 msgid "Dim" -msgstr "" +msgstr "어둑함" -#: src/view/com/composer/Composer.tsx:151 +#: src/lib/moderation/useLabelBehaviorDescription.ts:32 +#: src/lib/moderation/useLabelBehaviorDescription.ts:42 +#: src/lib/moderation/useLabelBehaviorDescription.ts:68 +#: src/screens/Moderation/index.tsx:347 +msgid "Disabled" +msgstr "비활성화ë¨" + +#: src/view/com/composer/Composer.tsx:506 msgid "Discard" msgstr "ì‚ì œ" -#: src/view/com/composer/Composer.tsx:145 -msgid "Discard draft" +#: src/view/com/composer/Composer.tsx:503 +msgid "Discard draft?" msgstr "초안 ì‚ì œ" -#: src/view/screens/Moderation.tsx:226 +#: src/screens/Moderation/index.tsx:524 +#: src/screens/Moderation/index.tsx:528 msgid "Discourage apps from showing my account to logged-out users" msgstr "ì•±ì´ ë¡œê·¸ì•„ì›ƒí•œ 사용ìžì—게 ë‚´ ê³„ì •ì„ í‘œì‹œí•˜ì§€ 않ë„ë¡ ì„¤ì •í•˜ê¸°" @@ -1125,13 +1124,9 @@ msgstr "ì•±ì´ ë¡œê·¸ì•„ì›ƒí•œ 사용ìžì—게 ë‚´ ê³„ì •ì„ í‘œì‹œí•˜ì§€ ì•Šë„ msgid "Discover new custom feeds" msgstr "새로운 맞춤 피드 찾아보기" -#: src/view/screens/Feeds.tsx:473 -#~ msgid "Discover new feeds" -#~ msgstr "새 피드 발견하기" - #: src/view/screens/Feeds.tsx:689 msgid "Discover New Feeds" -msgstr "" +msgstr "새 피드 발견하기" #: src/view/com/modals/EditProfile.tsx:192 msgid "Display name" @@ -1141,33 +1136,20 @@ msgstr "표시 ì´ë¦„" msgid "Display Name" msgstr "표시 ì´ë¦„" +#: src/lib/moderation/useGlobalLabelStrings.ts:39 +msgid "Does not include nudity." +msgstr "ë…¸ì¶œì„ í¬í•¨í•˜ì§€ 않ìŒ." + #: src/view/com/modals/ChangeHandle.tsx:487 msgid "Domain verified!" msgstr "ë„ë©”ì¸ì„ 확ì¸í–ˆìŠµë‹ˆë‹¤." -#: src/view/com/auth/create/Step1.tsx:170 -#~ msgid "Don't have an invite code?" -#~ msgstr "초대 코드가 없나요?" - -#: src/view/com/auth/onboarding/RecommendedFollows.tsx:86 -#: src/view/com/modals/EditImage.tsx:333 -#: src/view/com/modals/ListAddRemoveUsers.tsx:144 -#: src/view/com/modals/SelfLabel.tsx:157 -#: src/view/com/modals/Threadgate.tsx:129 -#: src/view/com/modals/Threadgate.tsx:132 -#: src/view/com/modals/UserAddRemoveLists.tsx:95 -#: src/view/com/modals/UserAddRemoveLists.tsx:98 -#: src/view/screens/PreferencesThreads.tsx:162 -msgctxt "action" -msgid "Done" -msgstr "완료" - +#: src/components/dialogs/BirthDateSettings.tsx:112 +#: src/components/dialogs/BirthDateSettings.tsx:118 #: src/view/com/auth/server-input/index.tsx:165 #: src/view/com/auth/server-input/index.tsx:166 #: src/view/com/modals/AddAppPasswords.tsx:226 #: src/view/com/modals/AltImage.tsx:139 -#: src/view/com/modals/ContentFilteringSettings.tsx:88 -#: src/view/com/modals/ContentFilteringSettings.tsx:96 #: src/view/com/modals/crop-image/CropImage.web.tsx:152 #: src/view/com/modals/InviteCodes.tsx:80 #: src/view/com/modals/InviteCodes.tsx:123 @@ -1178,6 +1160,19 @@ msgstr "완료" msgid "Done" msgstr "완료" +#: src/view/com/auth/onboarding/RecommendedFollows.tsx:86 +#: src/view/com/modals/EditImage.tsx:333 +#: src/view/com/modals/ListAddRemoveUsers.tsx:144 +#: src/view/com/modals/SelfLabel.tsx:157 +#: src/view/com/modals/Threadgate.tsx:129 +#: src/view/com/modals/Threadgate.tsx:132 +#: src/view/com/modals/UserAddRemoveLists.tsx:95 +#: src/view/com/modals/UserAddRemoveLists.tsx:98 +#: src/view/screens/PreferencesThreads.tsx:162 +msgctxt "action" +msgid "Done" +msgstr "완료" + #: src/view/com/modals/lang-settings/ConfirmLanguagesButton.tsx:42 msgid "Done{extraText}" msgstr "완료{extraText}" @@ -1186,20 +1181,20 @@ msgstr "완료{extraText}" msgid "Double tap to sign in" msgstr "ë‘ ë²ˆ íƒí•˜ì—¬ 로그ì¸í•©ë‹ˆë‹¤" -#: src/view/screens/Settings/index.tsx:755 +#: src/view/screens/Settings/index.tsx:773 msgid "Download Bluesky account data (repository)" -msgstr "" +msgstr "Bluesky ê³„ì • ë°ì´í„°ë¥¼ 다운로드합니다 (ì €ìž¥ì†Œ)" #: src/view/screens/Settings/ExportCarDialog.tsx:59 #: src/view/screens/Settings/ExportCarDialog.tsx:63 msgid "Download CAR file" -msgstr "" +msgstr "CAR íŒŒì¼ ë‹¤ìš´ë¡œë“œ" #: src/view/com/composer/text-input/TextInput.web.tsx:249 msgid "Drop to add images" msgstr "드ë¡í•˜ì—¬ ì´ë¯¸ì§€ 추가" -#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:111 +#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:116 msgid "Due to Apple policies, adult content can only be enabled on the web after completing sign up." msgstr "Apple ì •ì±…ìœ¼ë¡œ ì¸í•´ ì„±ì¸ ì½˜í…ì¸ ëŠ” ê°€ìž…ì„ ì™„ë£Œí•œ í›„ì— ì›¹ì—서만 사용 ì„¤ì •í• ìˆ˜ 있습니다." @@ -1211,6 +1206,10 @@ msgstr "예: 앨리스 ë¡œë²„ì¸ " 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:283 msgid "e.g. Great Posters" msgstr "예: ë©‹ì§„ í¬ìŠ¤í„°" @@ -1236,12 +1235,17 @@ msgctxt "action" msgid "Edit" msgstr "편집" +#: src/view/com/util/UserAvatar.tsx:295 +#: src/view/com/util/UserBanner.tsx:85 +msgid "Edit avatar" +msgstr "아바타 편집" + #: src/view/com/composer/photos/Gallery.tsx:144 #: src/view/com/modals/EditImage.tsx:207 msgid "Edit image" msgstr "ì´ë¯¸ì§€ 편집" -#: src/view/screens/ProfileList.tsx:433 +#: src/view/screens/ProfileList.tsx:403 msgid "Edit list details" msgstr "리스트 세부 ì •ë³´ 편집" @@ -1249,7 +1253,7 @@ msgstr "리스트 세부 ì •ë³´ 편집" msgid "Edit Moderation List" msgstr "ê²€í† ë¦¬ìŠ¤íŠ¸ 편집" -#: src/Navigation.tsx:244 +#: src/Navigation.tsx:256 #: src/view/screens/Feeds.tsx:434 #: src/view/screens/SavedFeeds.tsx:84 msgid "Edit My Feeds" @@ -1259,11 +1263,13 @@ msgstr "ë‚´ 피드 편집" msgid "Edit my profile" msgstr "ë‚´ 프로필 편집" -#: src/view/com/profile/ProfileHeader.tsx:418 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:172 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:161 msgid "Edit profile" msgstr "프로필 편집" -#: src/view/com/profile/ProfileHeader.tsx:423 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:175 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:164 msgid "Edit Profile" msgstr "프로필 편집" @@ -1312,7 +1318,7 @@ msgstr "ì´ë©”ì¼ ë³€ê²½ë¨" msgid "Email verified" msgstr "ì´ë©”ì¼ í™•ì¸ë¨" -#: src/view/screens/Settings/index.tsx:312 +#: src/view/screens/Settings/index.tsx:330 msgid "Email:" msgstr "ì´ë©”ì¼:" @@ -1320,12 +1326,12 @@ msgstr "ì´ë©”ì¼:" msgid "Enable {0} only" msgstr "{0}ë§Œ 사용" -#: src/view/com/modals/ContentFilteringSettings.tsx:167 -msgid "Enable Adult Content" +#: src/screens/Moderation/index.tsx:335 +msgid "Enable adult content" msgstr "ì„±ì¸ ì½˜í…ì¸ í™œì„±í™”" -#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:76 -#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:77 +#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:78 +#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:79 msgid "Enable adult content in your feeds" msgstr "피드ì—서 ì„±ì¸ ì½˜í…ì¸ ì‚¬ìš©" @@ -1341,7 +1347,11 @@ msgstr "미디어 í”Œë ˆì´ì–´ë¥¼ ì‚¬ìš©í• ì™¸ë¶€ 사ì´íЏ" msgid "Enable this setting to only see replies between people you follow." msgstr "ë‚´ê°€ 팔로우하는 사람들 ê°„ì˜ ë‹µê¸€ë§Œ 표시합니다." -#: src/view/screens/Profile.tsx:455 +#: src/screens/Moderation/index.tsx:345 +msgid "Enabled" +msgstr "활성화ë¨" + +#: src/screens/Profile/Sections/Feed.tsx:84 msgid "End of feed" msgstr "피드 ë" @@ -1352,7 +1362,7 @@ msgstr "ì´ ì•± ë¹„ë°€ë²ˆí˜¸ì˜ ì´ë¦„ì„ ìž…ë ¥í•˜ì„¸ìš”" #: src/components/dialogs/MutedWords.tsx:100 #: src/components/dialogs/MutedWords.tsx:101 msgid "Enter a word or tag" -msgstr "" +msgstr "단어 ë˜ëŠ” 태그 ìž…ë ¥" #: src/view/com/modals/VerifyEmail.tsx:105 msgid "Enter Confirmation Code" @@ -1360,7 +1370,7 @@ msgstr "í™•ì¸ ì½”ë“œ ìž…ë ¥" #: src/view/com/modals/ChangePassword.tsx:151 msgid "Enter the code you received to change your password." -msgstr "" +msgstr "비밀번호를 ë³€ê²½í•˜ë ¤ë©´ ë°›ì€ ì½”ë“œë¥¼ ìž…ë ¥í•˜ì„¸ìš”." #: src/view/com/modals/ChangeHandle.tsx:371 msgid "Enter the domain you want to use" @@ -1368,17 +1378,13 @@ msgstr "ì‚¬ìš©í• ë„ë©”ì¸ ìž…ë ¥" #: src/view/com/auth/login/ForgotPasswordForm.tsx:107 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 "ê³„ì •ì„ ë§Œë“¤ 때 사용한 ì´ë©”ì¼ì„ ìž…ë ¥í•©ë‹ˆë‹¤. 새 비밀번호를 ì„¤ì •í• ìˆ˜ 있ë„ë¡ \"ìž¬ì„¤ì • 코드\"를 보내드립니다." +msgstr "ê³„ì •ì„ ë§Œë“¤ 때 사용한 ì´ë©”ì¼ì„ ìž…ë ¥í•˜ì„¸ìš”. 새 비밀번호를 ì„¤ì •í• ìˆ˜ 있ë„ë¡ \"ìž¬ì„¤ì • 코드\"를 보내드립니다." +#: src/components/dialogs/BirthDateSettings.tsx:102 #: src/view/com/auth/create/Step1.tsx:228 -#: src/view/com/modals/BirthDateSettings.tsx:74 msgid "Enter your birth date" msgstr "ìƒë…„ì›”ì¼ì„ ìž…ë ¥í•˜ì„¸ìš”" -#: src/view/com/modals/Waitlist.tsx:78 -#~ msgid "Enter your email" -#~ msgstr "ì´ë©”ì¼ì„ ìž…ë ¥í•˜ì„¸ìš”" - #: src/view/com/auth/create/Step1.tsx:172 msgid "Enter your email address" msgstr "ì´ë©”ì¼ ì£¼ì†Œë¥¼ ìž…ë ¥í•˜ì„¸ìš”" @@ -1391,17 +1397,13 @@ msgstr "새 ì´ë©”ì¼ì„ ìž…ë ¥í•˜ì„¸ìš”" msgid "Enter your new email address below." msgstr "ì•„ëž˜ì— ìƒˆ ì´ë©”ì¼ ì£¼ì†Œë¥¼ ìž…ë ¥í•˜ì„¸ìš”." -#: src/view/com/auth/create/Step2.tsx:188 -#~ msgid "Enter your phone number" -#~ msgstr "ì „í™”ë²ˆí˜¸ë¥¼ ìž…ë ¥í•˜ì„¸ìš”" - #: src/view/com/auth/login/Login.tsx:99 msgid "Enter your username and password" msgstr "ì‚¬ìš©ìž ì´ë¦„ ë° ë¹„ë°€ë²ˆí˜¸ ìž…ë ¥" #: src/view/com/auth/create/Step3.tsx:67 msgid "Error receiving captcha response." -msgstr "" +msgstr "캡차 ì‘ë‹µì„ ìˆ˜ì‹ í•˜ëŠ” ë™ì•ˆ 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤." #: src/view/screens/Search/Search.tsx:110 msgid "Error:" @@ -1411,6 +1413,10 @@ msgstr "오류:" msgid "Everybody" msgstr "모ë‘" +#: src/lib/moderation/useReportOptions.ts:66 +msgid "Excessive mentions or replies" +msgstr "ê³¼ë„한 멘션 ë˜ëŠ” 답글" + #: src/view/com/modals/ChangeHandle.tsx:150 msgid "Exits handle change process" msgstr "핸들 변경 프로세스를 종료합니다" @@ -1424,10 +1430,6 @@ msgstr "ì´ë¯¸ì§€ 보기를 종료합니다" msgid "Exits inputting search query" msgstr "검색어 ìž…ë ¥ì„ ì¢…ë£Œí•©ë‹ˆë‹¤" -#: src/view/com/modals/Waitlist.tsx:138 -#~ msgid "Exits signing up for waitlist with {email}" -#~ msgstr "{email}ì„(를) ëŒ€ê¸°ìž ëª…ë‹¨ì— ë“±ë¡í•˜ëŠ” ê²ƒì„ ì¢…ë£Œí•©ë‹ˆë‹¤" - #: src/view/com/lightbox/Lightbox.web.tsx:163 msgid "Expand alt text" msgstr "대체 í…스트 확장" @@ -1437,14 +1439,22 @@ msgstr "대체 í…스트 확장" msgid "Expand or collapse the full post you are replying to" msgstr "ë‹µê¸€ì„ ë‹¬ê³ ìžˆëŠ” ì „ì²´ ê²Œì‹œë¬¼ì„ íŽ¼ì¹˜ê±°ë‚˜ ì ‘ìŠµë‹ˆë‹¤" -#: src/view/screens/Settings/index.tsx:753 +#: 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:771 msgid "Export my data" -msgstr "" +msgstr "ë‚´ ë°ì´í„° 내보내기" #: src/view/screens/Settings/ExportCarDialog.tsx:44 -#: src/view/screens/Settings/index.tsx:764 +#: src/view/screens/Settings/index.tsx:782 msgid "Export My Data" -msgstr "" +msgstr "ë‚´ ë°ì´í„° 내보내기" #: src/view/com/modals/EmbedConsent.tsx:64 msgid "External Media" @@ -1455,13 +1465,13 @@ msgstr "외부 미디어" 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:263 +#: src/Navigation.tsx:275 #: src/view/screens/PreferencesExternalEmbeds.tsx:52 -#: src/view/screens/Settings/index.tsx:657 +#: src/view/screens/Settings/index.tsx:675 msgid "External Media Preferences" msgstr "외부 미디어 ì„¤ì •" -#: src/view/screens/Settings/index.tsx:648 +#: src/view/screens/Settings/index.tsx:666 msgid "External media settings" msgstr "외부 미디어 ì„¤ì •" @@ -1474,7 +1484,7 @@ msgstr "앱 비밀번호를 만들지 못했습니다." msgid "Failed to create the list. Check your internet connection and try again." msgstr "리스트를 만들지 못했습니다. ì¸í„°ë„· ì—°ê²°ì„ í™•ì¸í•œ 후 다시 시ë„하세요." -#: src/view/com/util/forms/PostDropdownBtn.tsx:128 +#: src/view/com/util/forms/PostDropdownBtn.tsx:125 msgid "Failed to delete post, please try again" msgstr "ê²Œì‹œë¬¼ì„ ì‚ì œí•˜ì§€ 못했습니다. 다시 시ë„í•´ 주세요" @@ -1483,11 +1493,11 @@ msgstr "ê²Œì‹œë¬¼ì„ ì‚ì œí•˜ì§€ 못했습니다. 다시 시ë„í•´ 주세요" msgid "Failed to load recommended feeds" msgstr "추천 피드를 불러오지 못했습니다" -#: src/Navigation.tsx:194 +#: src/Navigation.tsx:196 msgid "Feed" msgstr "피드" -#: src/view/com/feeds/FeedSourceCard.tsx:231 +#: src/view/com/feeds/FeedSourceCard.tsx:218 msgid "Feed by {0}" msgstr "{0} ë‹˜ì˜ í”¼ë“œ" @@ -1495,20 +1505,16 @@ msgstr "{0} ë‹˜ì˜ í”¼ë“œ" msgid "Feed offline" msgstr "피드 오프ë¼ì¸" -#: src/view/com/feeds/FeedPage.tsx:143 -#~ msgid "Feed Preferences" -#~ msgstr "피드 ì„¤ì •" - #: src/view/shell/desktop/RightNav.tsx:61 #: src/view/shell/Drawer.tsx:311 msgid "Feedback" msgstr "피드백" -#: src/Navigation.tsx:452 +#: src/Navigation.tsx:464 #: src/view/screens/Feeds.tsx:419 #: src/view/screens/Feeds.tsx:524 -#: src/view/screens/Profile.tsx:184 -#: src/view/shell/bottom-bar/BottomBar.tsx:181 +#: src/view/screens/Profile.tsx:192 +#: src/view/shell/bottom-bar/BottomBar.tsx:183 #: src/view/shell/desktop/LeftNav.tsx:342 #: src/view/shell/Drawer.tsx:476 #: src/view/shell/Drawer.tsx:477 @@ -1521,11 +1527,15 @@ msgstr "피드는 콘í…ì¸ ë¥¼ íë ˆì´ì…˜í•˜ê¸° 위해 사용ìžì— ì˜í•´ ë§Œ #: src/view/screens/SavedFeeds.tsx:156 msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." -msgstr "피드는 사용ìžê°€ ì•½ê°„ì˜ ì½”ë”© ì „ë¬¸ ì§€ì‹ìœ¼ë¡œ êµ¬ì¶•í• ìˆ˜ 있는 맞춤 ì•Œê³ ë¦¬ì¦˜ìž…ë‹ˆë‹¤. <0/>ì—서 ìžì„¸í•œ ë‚´ìš©ì„ í™•ì¸í•˜ì„¸ìš”." +msgstr "피드는 사용ìžê°€ ì•½ê°„ì˜ ì½”ë”© ì „ë¬¸ ì§€ì‹ë§Œìœ¼ë¡œ êµ¬ì¶•í• ìˆ˜ 있는 맞춤 ì•Œê³ ë¦¬ì¦˜ìž…ë‹ˆë‹¤. <0/>ì—서 ìžì„¸í•œ ë‚´ìš©ì„ í™•ì¸í•˜ì„¸ìš”." #: src/screens/Onboarding/StepTopicalFeeds.tsx:76 msgid "Feeds can be topical as well!" -msgstr "í”¼ë“œë„ í™”ì œê°€ ë 수 있습니다!" +msgstr "ì£¼ì œ 기반 í”¼ë“œë„ ìžˆìŠµë‹ˆë‹¤!" + +#: src/lib/moderation/useLabelBehaviorDescription.ts:66 +msgid "Filter from feeds" +msgstr "피드ì—서 í•„í„°ë§" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Finalizing" @@ -1545,21 +1555,17 @@ msgstr "Blueskyì—서 ì‚¬ìš©ìž ì°¾ê¸°" msgid "Find users with the search tool on the right" msgstr "ì˜¤ë¥¸ìª½ì˜ ê²€ìƒ‰ ë„구로 ì‚¬ìš©ìž ì°¾ê¸°" -#: src/view/com/auth/onboarding/RecommendedFollowsItem.tsx:150 +#: src/view/com/auth/onboarding/RecommendedFollowsItem.tsx:153 msgid "Finding similar accounts..." msgstr "ìœ ì‚¬í•œ ê³„ì •ì„ ì°¾ëŠ” 중…" #: src/view/screens/PreferencesFollowingFeed.tsx:111 msgid "Fine-tune the content you see on your Following feed." -msgstr "" - -#: src/view/screens/PreferencesHomeFeed.tsx:111 -#~ msgid "Fine-tune the content you see on your home screen." -#~ msgstr "홈 í™”ë©´ì— í‘œì‹œë˜ëŠ” 콘í…ì¸ ë¥¼ 미세 ì¡°ì •í•©ë‹ˆë‹¤." +msgstr "팔로우 중 í”¼ë“œì— í‘œì‹œë˜ëŠ” 콘í…ì¸ ë¥¼ 미세 ì¡°ì •í•©ë‹ˆë‹¤." #: src/view/screens/PreferencesThreads.tsx:60 msgid "Fine-tune the discussion threads." -msgstr "í† ë¡ ìŠ¤ë ˆë“œë¥¼ 미세 ì¡°ì •í•©ë‹ˆë‹¤." +msgstr "대화 ìŠ¤ë ˆë“œë¥¼ 미세 ì¡°ì •í•©ë‹ˆë‹¤." #: src/screens/Onboarding/index.tsx:38 msgid "Fitness" @@ -1579,22 +1585,27 @@ msgid "Flip vertically" msgstr "세로로 뒤집기" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:181 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:136 -#: src/view/com/profile/ProfileHeader.tsx:513 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:229 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:139 msgid "Follow" msgstr "팔로우" -#: src/view/com/profile/FollowButton.tsx:64 +#: src/view/com/profile/FollowButton.tsx:69 msgctxt "action" msgid "Follow" msgstr "팔로우" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:58 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:122 -#: src/view/com/profile/ProfileHeader.tsx:504 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:214 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:125 msgid "Follow {0}" msgstr "{0} ë‹˜ì„ íŒ”ë¡œìš°" +#: src/view/com/profile/ProfileMenu.tsx:242 +#: src/view/com/profile/ProfileMenu.tsx:253 +msgid "Follow Account" +msgstr "ê³„ì • 팔로우" + #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:179 msgid "Follow All" msgstr "ëª¨ë‘ íŒ”ë¡œìš°" @@ -1605,9 +1616,9 @@ msgstr "ì„ íƒí•œ ê³„ì •ì„ íŒ”ë¡œìš°í•˜ê³ ë‹¤ìŒ ë‹¨ê³„ë¥¼ ê³„ì† ì§„í–‰í•©ë‹ˆ #: src/view/com/auth/onboarding/RecommendedFollows.tsx:64 msgid "Follow some users to get started. We can recommend you more users based on who you find interesting." -msgstr "ì¼ë¶€ 사용ìžë¥¼ 팔로우하여 시작하세요. 관심 있는 사용ìžë¥¼ 기반으로 ë” ë§Žì€ ì‚¬ìš©ìžë¥¼ 추천해 드릴 수 있습니다." +msgstr "ì‹œìž‘í•˜ë ¤ë©´ ì‚¬ìš©ìž ëª‡ ëª…ì„ íŒ”ë¡œìš°í•´ 보세요. 누구ì—게 ê´€ì‹¬ì´ ìžˆëŠ”ì§€ë¥¼ 기반으로 ë” ë§Žì€ ì‚¬ìš©ìžë¥¼ 추천해 드릴 수 있습니다." -#: src/view/com/profile/ProfileCard.tsx:194 +#: src/view/com/profile/ProfileCard.tsx:214 msgid "Followed by {0}" msgstr "{0} ë‹˜ì´ íŒ”ë¡œìš°í•¨" @@ -1627,29 +1638,29 @@ msgstr "ë‹˜ì´ ë‚˜ë¥¼ 팔로우했습니다" msgid "Followers" msgstr "팔로워" -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:136 -#: src/view/com/profile/ProfileHeader.tsx:495 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:227 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:139 #: src/view/screens/ProfileFollows.tsx:25 msgid "Following" msgstr "팔로우 중" -#: src/view/com/profile/ProfileHeader.tsx:149 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:89 msgid "Following {0}" -msgstr "{0} 팔로우 중" +msgstr "{0} ë‹˜ì„ íŒ”ë¡œìš°í–ˆìŠµë‹ˆë‹¤" -#: src/Navigation.tsx:250 +#: src/Navigation.tsx:262 #: src/view/com/home/HomeHeaderLayout.web.tsx:50 #: src/view/com/home/HomeHeaderLayoutMobile.tsx:84 #: src/view/screens/PreferencesFollowingFeed.tsx:104 -#: src/view/screens/Settings/index.tsx:543 +#: src/view/screens/Settings/index.tsx:561 msgid "Following Feed Preferences" -msgstr "" +msgstr "팔로우 중 피드 ì„¤ì •" -#: src/view/com/profile/ProfileHeader.tsx:546 +#: src/screens/Profile/Header/Handle.tsx:24 msgid "Follows you" msgstr "나를 팔로우함" -#: src/view/com/profile/ProfileCard.tsx:141 +#: src/view/com/profile/ProfileCard.tsx:139 msgid "Follows You" msgstr "나를 팔로우함" @@ -1678,12 +1689,16 @@ msgstr "비밀번호 분실" msgid "Forgot Password" msgstr "비밀번호 분실" +#: src/lib/moderation/useReportOptions.ts:52 +msgid "Frequently Posts Unwanted Content" +msgstr "ìž¦ì€ ì›ì¹˜ 않는 콘í…ì¸ ê²Œì‹œ" + #: src/screens/Hashtag.tsx:108 #: src/screens/Hashtag.tsx:148 msgid "From @{sanitizedAuthor}" -msgstr "" +msgstr "@{sanitizedAuthor} ë‹˜ì˜ íƒœê·¸" -#: src/view/com/posts/FeedItem.tsx:189 +#: src/view/com/posts/FeedItem.tsx:183 msgctxt "from-feed" msgid "From <0/>" msgstr "<0/>ì—서" @@ -1697,20 +1712,29 @@ msgstr "갤러리" msgid "Get Started" msgstr "시작하기" +#: src/lib/moderation/useReportOptions.ts:37 +msgid "Glaring violations of law or terms of service" +msgstr "명백한 ë²•ë¥ ë˜ëŠ” 서비스 약관 위반 행위" + +#: src/components/moderation/ScreenHider.tsx:143 +#: src/components/moderation/ScreenHider.tsx:152 #: src/view/com/auth/LoggedOut.tsx:81 #: src/view/com/auth/LoggedOut.tsx:82 -#: src/view/com/util/moderation/ScreenHider.tsx:123 #: src/view/shell/desktop/LeftNav.tsx:104 msgid "Go back" msgstr "뒤로" -#: src/view/screens/ProfileFeed.tsx:106 +#: src/screens/Profile/ErrorState.tsx:62 +#: src/screens/Profile/ErrorState.tsx:66 #: src/view/screens/ProfileFeed.tsx:111 -#: src/view/screens/ProfileList.tsx:902 -#: src/view/screens/ProfileList.tsx:907 +#: src/view/screens/ProfileFeed.tsx:116 +#: src/view/screens/ProfileList.tsx:916 +#: src/view/screens/ProfileList.tsx:921 msgid "Go Back" msgstr "뒤로" +#: src/components/ReportDialog/SelectReportOptionView.tsx:73 +#: src/components/ReportDialog/SubmitView.tsx:104 #: src/screens/Onboarding/Layout.tsx:104 #: src/screens/Onboarding/Layout.tsx:193 msgid "Go back to previous step" @@ -1729,21 +1753,25 @@ msgstr "@{queryMaybeHandle}(으)로 ì´ë™" msgid "Go to next" msgstr "다ìŒ" +#: src/lib/moderation/useGlobalLabelStrings.ts:46 +msgid "Graphic Media" +msgstr "그래픽 미디어" + #: src/view/com/modals/ChangeHandle.tsx:265 msgid "Handle" msgstr "핸들" -#: src/Navigation.tsx:270 -msgid "Hashtag" -msgstr "" +#: src/lib/moderation/useReportOptions.ts:32 +msgid "Harassment, trolling, or intolerance" +msgstr "ê´´ë¡íž˜, ë¶„ìŸ ìœ ë°œ ë˜ëŠ” 차별" -#: src/components/RichText.tsx:188 -#~ msgid "Hashtag: {tag}" -#~ msgstr "" +#: src/Navigation.tsx:282 +msgid "Hashtag" +msgstr "해시태그" #: src/components/RichText.tsx:190 msgid "Hashtag: #{tag}" -msgstr "" +msgstr "해시태그: #{tag}" #: src/view/com/auth/create/CreateAccount.tsx:208 msgid "Having trouble?" @@ -1770,41 +1798,42 @@ msgstr "다ìŒì€ 사용ìžì˜ 관심사를 기반으로 한 몇 가지 ì£¼ì œë³ msgid "Here is your app password." msgstr "앱 비밀번호입니다." -#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:41 -#: src/view/com/modals/ContentFilteringSettings.tsx:251 -#: src/view/com/util/moderation/ContentHider.tsx:105 -#: src/view/com/util/moderation/PostHider.tsx:108 +#: src/components/moderation/ContentHider.tsx:115 +#: src/components/moderation/GlobalModerationLabelPref.tsx:43 +#: src/components/moderation/PostHider.tsx:107 +#: 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/screens/Onboarding/StepModeration/ModerationOption.tsx:52 +#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:76 +#: src/view/com/util/forms/PostDropdownBtn.tsx:328 msgid "Hide" msgstr "숨기기" -#: src/view/com/modals/ContentFilteringSettings.tsx:224 -#: src/view/com/notifications/FeedItem.tsx:326 +#: src/view/com/notifications/FeedItem.tsx:325 msgctxt "action" msgid "Hide" msgstr "숨기기" #: src/view/com/util/forms/PostDropdownBtn.tsx:276 -#: src/view/com/util/forms/PostDropdownBtn.tsx:287 +#: src/view/com/util/forms/PostDropdownBtn.tsx:278 msgid "Hide post" msgstr "게시물 숨기기" -#: src/view/com/util/moderation/ContentHider.tsx:67 -#: src/view/com/util/moderation/PostHider.tsx:61 +#: src/components/moderation/ContentHider.tsx:67 +#: src/components/moderation/PostHider.tsx:64 msgid "Hide the content" msgstr "콘í…ì¸ ìˆ¨ê¸°ê¸°" -#: src/view/com/util/forms/PostDropdownBtn.tsx:280 +#: src/view/com/util/forms/PostDropdownBtn.tsx:325 msgid "Hide this post?" msgstr "ì´ ê²Œì‹œë¬¼ì„ ìˆ¨ê¸°ì‹œê² ìŠµë‹ˆê¹Œ?" -#: src/view/com/notifications/FeedItem.tsx:316 +#: src/view/com/notifications/FeedItem.tsx:315 msgid "Hide user list" msgstr "ì‚¬ìš©ìž ë¦¬ìŠ¤íŠ¸ 숨기기" -#: src/view/com/profile/ProfileHeader.tsx:487 -msgid "Hides posts from {0} in your feed" -msgstr "피드ì—서 {0} ë‹˜ì˜ ê²Œì‹œë¬¼ì„ ìˆ¨ê¹ë‹ˆë‹¤" - #: src/view/com/posts/FeedErrorMessage.tsx:111 msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." msgstr "피드 ì„œë²„ì— ì—°ê²°í•˜ëŠ” 중 ì–´ë–¤ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. 피드 ì†Œìœ ìžì—게 ì´ ë¬¸ì œì— ëŒ€í•´ ì•Œë ¤ì£¼ì„¸ìš”." @@ -1825,21 +1854,22 @@ msgstr "피드 서버ì—서 ìž˜ëª»ëœ ì‘ë‹µì„ ë³´ëƒˆìŠµë‹ˆë‹¤. 피드 ì†Œìœ ìž msgid "Hmm, we're having trouble finding this feed. It may have been deleted." msgstr "ì´ í”¼ë“œë¥¼ 찾는 ë° ë¬¸ì œê°€ 있습니다. 피드가 ì‚ì œë˜ì—ˆì„ 수 있습니다." -#: src/Navigation.tsx:442 -#: src/view/shell/bottom-bar/BottomBar.tsx:137 +#: src/screens/Moderation/index.tsx:61 +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:454 +#: src/view/shell/bottom-bar/BottomBar.tsx:139 #: src/view/shell/desktop/LeftNav.tsx:306 #: src/view/shell/Drawer.tsx:398 #: src/view/shell/Drawer.tsx:399 msgid "Home" msgstr "홈" -#: src/Navigation.tsx:247 -#: src/view/com/pager/FeedsTabBarMobile.tsx:123 -#: src/view/screens/PreferencesHomeFeed.tsx:104 -#: src/view/screens/Settings/index.tsx:543 -#~ msgid "Home Feed Preferences" -#~ msgstr "홈 피드 ì„¤ì •" - #: src/view/com/auth/create/Step1.tsx:75 #: src/view/com/auth/login/ForgotPasswordForm.tsx:120 msgid "Hosting provider" @@ -1869,9 +1899,21 @@ msgstr "대체 í…스트가 긴 경우 대체 í…스트 확장 ìƒíƒœë¥¼ ì „í™˜í msgid "If none are selected, suitable for all ages." msgstr "ì•„ë¬´ê²ƒë„ ì„ íƒí•˜ì§€ 않으면 ëª¨ë“ ì—°ë ¹ëŒ€ì— ì 합하다는 뜻입니다." +#: src/view/screens/ProfileList.tsx:610 +msgid "If you delete this list, you won't be able to recover it." +msgstr "ì´ ë¦¬ìŠ¤íŠ¸ë¥¼ ì‚ì œí•˜ë©´ 다시 ë³µêµ¬í• ìˆ˜ 없습니다." + +#: src/view/com/util/forms/PostDropdownBtn.tsx:316 +msgid "If you remove this post, you won't be able to recover it." +msgstr "ì´ ê²Œì‹œë¬¼ì„ ì‚ì œí•˜ë©´ 다시 ë³µêµ¬í• ìˆ˜ 없습니다." + #: src/view/com/modals/ChangePassword.tsx:146 msgid "If you want to change your password, we will send you a code to verify that this is your account." -msgstr "" +msgstr "비밀번호를 ë³€ê²½í•˜ê³ ì‹¶ë‹¤ë©´ ë³¸ì¸ ê³„ì •ìž„ì„ í™•ì¸í• 수 있는 코드를 ë³´ë‚´ë“œë¦¬ê² ìŠµë‹ˆë‹¤." + +#: src/lib/moderation/useReportOptions.ts:36 +msgid "Illegal and Urgent" +msgstr "불법 ë° ê¸´ê¸‰ 사í•" #: src/view/com/util/images/Gallery.tsx:38 msgid "Image" @@ -1881,10 +1923,9 @@ msgstr "ì´ë¯¸ì§€" msgid "Image alt text" msgstr "ì´ë¯¸ì§€ 대체 í…스트" -#: src/view/com/util/UserAvatar.tsx:311 -#: src/view/com/util/UserBanner.tsx:118 -msgid "Image options" -msgstr "ì´ë¯¸ì§€ 옵션" +#: src/lib/moderation/useReportOptions.ts:47 +msgid "Impersonation or false claims about identity or affiliation" +msgstr "ì‹ ì› ë˜ëŠ” 소ì†ì— 대한 ì‚¬ì¹ ë˜ëŠ” 허위 주장" #: src/view/com/auth/login/SetNewPasswordForm.tsx:138 msgid "Input code sent to your email for password reset" @@ -1914,10 +1955,6 @@ msgstr "새 비밀번호를 ìž…ë ¥í•©ë‹ˆë‹¤" msgid "Input password for account deletion" msgstr "ê³„ì •ì„ ì‚ì œí•˜ê¸° 위해 비밀번호를 ìž…ë ¥í•©ë‹ˆë‹¤" -#: src/view/com/auth/create/Step2.tsx:196 -#~ msgid "Input phone number for SMS verification" -#~ msgstr "SMS ì¸ì¦ì— ì‚¬ìš©í• ì „í™”ë²ˆí˜¸ë¥¼ ìž…ë ¥í•©ë‹ˆë‹¤" - #: src/view/com/auth/login/LoginForm.tsx:230 msgid "Input the password tied to {identifier}" msgstr "{identifier}ì— ì—°ê²°ëœ ë¹„ë°€ë²ˆí˜¸ë¥¼ ìž…ë ¥í•©ë‹ˆë‹¤" @@ -1926,14 +1963,6 @@ msgstr "{identifier}ì— ì—°ê²°ëœ ë¹„ë°€ë²ˆí˜¸ë¥¼ ìž…ë ¥í•©ë‹ˆë‹¤" msgid "Input the username or email address you used at signup" msgstr "가입 시 사용한 ì‚¬ìš©ìž ì´ë¦„ ë˜ëŠ” ì´ë©”ì¼ ì£¼ì†Œë¥¼ ìž…ë ¥í•©ë‹ˆë‹¤" -#: src/view/com/auth/create/Step2.tsx:271 -#~ msgid "Input the verification code we have texted to you" -#~ msgstr "ë¬¸ìž ë©”ì‹œì§€ë¡œ ì „ì†¡ëœ ì¸ì¦ 코드를 ìž…ë ¥í•©ë‹ˆë‹¤" - -#: src/view/com/modals/Waitlist.tsx:90 -#~ msgid "Input your email to get on the Bluesky waitlist" -#~ msgstr "Bluesky ëŒ€ê¸°ìž ëª…ë‹¨ì— ë“±ë¡í•˜ë ¤ë©´ ì´ë©”ì¼ì„ ìž…ë ¥í•©ë‹ˆë‹¤" - #: src/view/com/auth/login/LoginForm.tsx:229 msgid "Input your password" msgstr "비밀번호를 ìž…ë ¥í•©ë‹ˆë‹¤" @@ -1942,7 +1971,7 @@ msgstr "비밀번호를 ìž…ë ¥í•©ë‹ˆë‹¤" msgid "Input your user handle" msgstr "ì‚¬ìš©ìž í•¸ë“¤ì„ ìž…ë ¥í•©ë‹ˆë‹¤" -#: src/view/com/post-thread/PostThreadItem.tsx:226 +#: src/view/com/post-thread/PostThreadItem.tsx:225 msgid "Invalid or unsupported post record" msgstr "ìœ íš¨í•˜ì§€ 않거나 ì§€ì›ë˜ì§€ 않는 게시물 기ë¡" @@ -1950,10 +1979,6 @@ msgstr "ìœ íš¨í•˜ì§€ 않거나 ì§€ì›ë˜ì§€ 않는 게시물 기ë¡" msgid "Invalid username or password" msgstr "ìž˜ëª»ëœ ì‚¬ìš©ìž ì´ë¦„ ë˜ëŠ” 비밀번호" -#: src/view/screens/Settings.tsx:411 -#~ msgid "Invite" -#~ msgstr "초대" - #: src/view/com/modals/InviteCodes.tsx:93 msgid "Invite a Friend" msgstr "친구 초대하기" @@ -1971,10 +1996,6 @@ msgstr "초대 코드가 올바르지 않습니다. 코드를 올바르게 ìž…ë msgid "Invite codes: {0} available" msgstr "초대 코드: {0}ê°œ 사용 가능" -#: src/view/shell/Drawer.tsx:645 -#~ msgid "Invite codes: {invitesAvailable} available" -#~ msgstr "초대 코드: {invitesAvailable}ê°œ 사용 가능" - #: src/view/com/modals/InviteCodes.tsx:169 msgid "Invite codes: 1 available" msgstr "초대 코드: 1ê°œ 사용 가능" @@ -1988,37 +2009,56 @@ msgstr "ë‚´ê°€ 팔로우하는 ì‚¬ëžŒë“¤ì˜ ê²Œì‹œë¬¼ì´ ì˜¬ë¼ì˜¤ëŠ” 대로 표 msgid "Jobs" msgstr "채용" -#: src/view/com/modals/Waitlist.tsx:67 -#~ msgid "Join the waitlist" -#~ msgstr "ëŒ€ê¸°ìž ëª…ë‹¨ 등ë¡" - -#: src/view/com/auth/create/Step1.tsx:174 -#: src/view/com/auth/create/Step1.tsx:178 -#~ msgid "Join the waitlist." -#~ msgstr "ëŒ€ê¸°ìž ëª…ë‹¨ì— ë“±ë¡í•˜ì„¸ìš”." - -#: src/view/com/modals/Waitlist.tsx:128 -#~ msgid "Join Waitlist" -#~ msgstr "ëŒ€ê¸°ìž ëª…ë‹¨ 등ë¡" - #: src/screens/Onboarding/index.tsx:24 msgid "Journalism" msgstr "ì €ë„리즘" +#: src/components/moderation/LabelsOnMe.tsx:59 +msgid "label has been placed on this {labelTarget}" +msgstr "ì´ {labelTarget}ì— ë¼ë²¨ì´ ì§€ì •ë˜ì—ˆìŠµë‹ˆë‹¤" + +#: src/components/moderation/ContentHider.tsx:144 +msgid "Labeled by {0}." +msgstr "{0} ë‹˜ì´ ë¼ë²¨ ì§€ì •í•¨." + +#: src/components/moderation/ContentHider.tsx:142 +msgid "Labeled by the author." +msgstr "작성ìžê°€ ë¼ë²¨ ì§€ì •í•¨." + +#: src/view/screens/Profile.tsx:186 +msgid "Labels" +msgstr "ë¼ë²¨" + +#: src/screens/Profile/Sections/Labels.tsx:161 +msgid "Labels are annotations on users and content. They can be used to hide, warn, and categorize the network." +msgstr "ë¼ë²¨ì€ ì‚¬ìš©ìž ë° ì½˜í…ì¸ ì— ëŒ€í•œ 주ì„입니다. 네트워í¬ë¥¼ ìˆ¨ê¸°ê³ , ê²½ê³ í•˜ê³ , 분류하는 ë° ì‚¬ìš©í• ìˆ˜ 있습니다." + +#: src/components/moderation/LabelsOnMe.tsx:61 +msgid "labels have been placed on this {labelTarget}" +msgstr "ë¼ë²¨ì´ {labelTarget}ì— ì§€ì •ë˜ì—ˆìŠµë‹ˆë‹¤" + +#: src/components/moderation/LabelsOnMeDialog.tsx:63 +msgid "Labels on your account" +msgstr "ë‚´ ê³„ì •ì˜ ë¼ë²¨" + +#: src/components/moderation/LabelsOnMeDialog.tsx:65 +msgid "Labels on your content" +msgstr "ë‚´ 콘í…ì¸ ì˜ ë¼ë²¨" + #: src/view/com/composer/select-language/SelectLangBtn.tsx:104 msgid "Language selection" msgstr "언어 ì„ íƒ" -#: src/view/screens/Settings/index.tsx:594 +#: src/view/screens/Settings/index.tsx:612 msgid "Language settings" msgstr "언어 ì„¤ì •" -#: src/Navigation.tsx:142 +#: src/Navigation.tsx:144 #: src/view/screens/LanguageSettings.tsx:89 msgid "Language Settings" msgstr "언어 ì„¤ì •" -#: src/view/screens/Settings/index.tsx:603 +#: src/view/screens/Settings/index.tsx:621 msgid "Languages" msgstr "언어" @@ -2026,28 +2066,28 @@ msgstr "언어" msgid "Last step!" msgstr "마지막 단계예요!" -#: src/view/com/util/moderation/ContentHider.tsx:103 -msgid "Learn more" -msgstr "ë” ì•Œì•„ë³´ê¸°" - -#: src/view/com/util/moderation/PostAlerts.tsx:47 -#: src/view/com/util/moderation/ProfileHeaderAlerts.tsx:65 -#: src/view/com/util/moderation/ScreenHider.tsx:104 +#: src/components/moderation/ScreenHider.tsx:128 msgid "Learn More" msgstr "ë” ì•Œì•„ë³´ê¸°" -#: src/view/com/util/moderation/ContentHider.tsx:85 -#: src/view/com/util/moderation/PostAlerts.tsx:40 -#: src/view/com/util/moderation/PostHider.tsx:78 -#: src/view/com/util/moderation/ProfileHeaderAlerts.tsx:49 -#: src/view/com/util/moderation/ScreenHider.tsx:101 +#: src/components/moderation/ContentHider.tsx:65 +#: src/components/moderation/ContentHider.tsx:128 +msgid "Learn more about the moderation applied to this content." +msgstr "ì´ ì½˜í…ì¸ ì— ì ìš©ëœ ê²€í† ì„¤ì •ì— ëŒ€í•´ ìžì„¸ížˆ 알아보세요." + +#: src/components/moderation/PostHider.tsx:85 +#: src/components/moderation/ScreenHider.tsx:125 msgid "Learn more about this warning" msgstr "ì´ ê²½ê³ ì— ëŒ€í•´ ë” ì•Œì•„ë³´ê¸°" -#: src/view/screens/Moderation.tsx:262 +#: src/screens/Moderation/index.tsx:555 msgid "Learn more about what is public on Bluesky." msgstr "Blueskyì—서 공개ë˜ëŠ” í•ëª©ì— ëŒ€í•´ ìžì„¸ížˆ 알아보세요." +#: src/components/moderation/ContentHider.tsx:152 +msgid "Learn more." +msgstr "ë” ì•Œì•„ë³´ê¸°" + #: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:82 msgid "Leave them all unchecked to see any language." msgstr "ëª¨ë“ ì–¸ì–´ë¥¼ ë³´ë ¤ë©´ ëª¨ë‘ ì„ íƒí•˜ì§€ ì•Šì€ ìƒíƒœë¡œ ë‘세요." @@ -2060,7 +2100,7 @@ msgstr "Bluesky ë– ë‚˜ê¸°" msgid "left to go." msgstr "명 남았습니다." -#: src/view/screens/Settings/index.tsx:278 +#: src/view/screens/Settings/index.tsx:292 msgid "Legacy storage cleared, you need to restart the app now." msgstr "ë ˆê±°ì‹œ ìŠ¤í† ë¦¬ì§€ê°€ 지워졌으며 지금 ì•±ì„ ë‹¤ì‹œ 시작해야 합니다." @@ -2073,37 +2113,42 @@ msgstr "비밀번호를 ìž¬ì„¤ì •í•´ 봅시다!" msgid "Let's go!" msgstr "출발!" -#: src/view/com/util/UserAvatar.tsx:248 -#: src/view/com/util/UserBanner.tsx:62 -msgid "Library" -msgstr "ë¼ì´ë¸ŒëŸ¬ë¦¬" - -#: src/view/screens/Settings/index.tsx:479 +#: src/view/screens/Settings/index.tsx:497 msgid "Light" msgstr "ë°ìŒ" -#: src/view/com/util/post-ctrls/PostCtrls.tsx:182 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:185 msgid "Like" msgstr "좋아요" -#: src/view/screens/ProfileFeed.tsx:591 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:257 +#: src/view/screens/ProfileFeed.tsx:572 msgid "Like this feed" msgstr "ì´ í”¼ë“œì— ì¢‹ì•„ìš” 표시" -#: src/Navigation.tsx:199 +#: src/components/LikesDialog.tsx:87 +#: src/Navigation.tsx:201 +#: src/Navigation.tsx:206 msgid "Liked by" msgstr "좋아요 표시한 사용ìž" +#: src/screens/Profile/ProfileLabelerLikedBy.tsx:42 #: src/view/screens/PostLikedBy.tsx:27 #: src/view/screens/ProfileFeedLikedBy.tsx:27 msgid "Liked By" msgstr "좋아요 표시한 사용ìž" -#: src/view/com/feeds/FeedSourceCard.tsx:279 +#: src/view/com/feeds/FeedSourceCard.tsx:268 msgid "Liked by {0} {1}" msgstr "{0}ëª…ì˜ ì‚¬ìš©ìžê°€ 좋아함" -#: src/view/screens/ProfileFeed.tsx:606 +#: src/components/LabelingServiceCard/index.tsx:72 +msgid "Liked by {count} {0}" +msgstr "{count}ëª…ì˜ ì‚¬ìš©ìžê°€ 좋아함" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:277 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:291 +#: src/view/screens/ProfileFeed.tsx:587 msgid "Liked by {likeCount} {0}" msgstr "{likeCount}ëª…ì˜ ì‚¬ìš©ìžê°€ 좋아함" @@ -2115,15 +2160,15 @@ msgstr "ë‹˜ì´ ë‚´ 맞춤 피드를 좋아합니다" msgid "liked your post" msgstr "ë‹˜ì´ ë‚´ ê²Œì‹œë¬¼ì„ ì¢‹ì•„í•©ë‹ˆë‹¤" -#: src/view/screens/Profile.tsx:183 +#: src/view/screens/Profile.tsx:191 msgid "Likes" msgstr "좋아요" -#: src/view/com/post-thread/PostThreadItem.tsx:183 +#: src/view/com/post-thread/PostThreadItem.tsx:182 msgid "Likes on this post" msgstr "ì´ ê²Œì‹œë¬¼ì„ ì¢‹ì•„ìš” 표시합니다" -#: src/Navigation.tsx:168 +#: src/Navigation.tsx:170 msgid "List" msgstr "리스트" @@ -2131,15 +2176,15 @@ msgstr "리스트" msgid "List Avatar" msgstr "리스트 아바타" -#: src/view/screens/ProfileList.tsx:324 +#: src/view/screens/ProfileList.tsx:311 msgid "List blocked" msgstr "리스트 차단ë¨" -#: src/view/com/feeds/FeedSourceCard.tsx:233 +#: src/view/com/feeds/FeedSourceCard.tsx:220 msgid "List by {0}" msgstr "{0} ë‹˜ì˜ ë¦¬ìŠ¤íŠ¸" -#: src/view/screens/ProfileList.tsx:378 +#: src/view/screens/ProfileList.tsx:355 msgid "List deleted" msgstr "리스트 ì‚ì œë¨" @@ -2151,24 +2196,25 @@ msgstr "리스트 뮤트ë¨" msgid "List Name" msgstr "리스트 ì´ë¦„" -#: src/view/screens/ProfileList.tsx:343 +#: src/view/screens/ProfileList.tsx:325 msgid "List unblocked" msgstr "리스트 차단 í•´ì œë¨" -#: src/view/screens/ProfileList.tsx:302 +#: src/view/screens/ProfileList.tsx:297 msgid "List unmuted" msgstr "리스트 언뮤트ë¨" -#: src/Navigation.tsx:112 -#: src/view/screens/Profile.tsx:185 +#: src/Navigation.tsx:114 +#: src/view/screens/Profile.tsx:187 +#: src/view/screens/Profile.tsx:193 #: src/view/shell/desktop/LeftNav.tsx:379 #: src/view/shell/Drawer.tsx:492 #: src/view/shell/Drawer.tsx:493 msgid "Lists" msgstr "리스트" -#: src/view/com/post-thread/PostThread.tsx:333 -#: src/view/com/post-thread/PostThread.tsx:341 +#: src/view/com/post-thread/PostThread.tsx:334 +#: src/view/com/post-thread/PostThread.tsx:342 msgid "Load more posts" msgstr "ë” ë§Žì€ ê²Œì‹œë¬¼ 불러오기" @@ -2176,10 +2222,10 @@ msgstr "ë” ë§Žì€ ê²Œì‹œë¬¼ 불러오기" msgid "Load new notifications" msgstr "새 알림 불러오기" +#: src/screens/Profile/Sections/Feed.tsx:70 #: src/view/com/feeds/FeedPage.tsx:115 -#: src/view/screens/Profile.tsx:440 #: src/view/screens/ProfileFeed.tsx:495 -#: src/view/screens/ProfileList.tsx:681 +#: src/view/screens/ProfileList.tsx:695 msgid "Load new posts" msgstr "새 게시물 불러오기" @@ -2187,11 +2233,7 @@ msgstr "새 게시물 불러오기" msgid "Loading..." msgstr "불러오는 중…" -#: src/view/com/modals/ServerInput.tsx:50 -#~ msgid "Local dev server" -#~ msgstr "로컬 개발 서버" - -#: src/Navigation.tsx:209 +#: src/Navigation.tsx:221 msgid "Log" msgstr "로그" @@ -2202,7 +2244,7 @@ msgstr "로그" msgid "Log out" msgstr "로그아웃" -#: src/view/screens/Moderation.tsx:155 +#: src/screens/Moderation/index.tsx:448 msgid "Logged-out visibility" msgstr "로그아웃 표시" @@ -2216,17 +2258,17 @@ msgstr "ì´ê³³ì´ ë‹¹ì‹ ì´ ê°€ê³ ìž í•˜ëŠ” ê³³ì¸ì§€ 확ì¸í•˜ì„¸ìš”!" #: src/components/dialogs/MutedWords.tsx:83 msgid "Manage your muted words and tags" -msgstr "" +msgstr "뮤트한 단어 ë° íƒœê·¸ 관리" #: src/view/com/auth/create/Step2.tsx:118 msgid "May not be longer than 253 characters" -msgstr "" +msgstr "253ìžë¥¼ ë„˜ì„ ìˆ˜ 없습니다" #: src/view/com/auth/create/Step2.tsx:109 msgid "May only contain letters and numbers" -msgstr "" +msgstr "문ìžì™€ 숫ìžë§Œ ìž…ë ¥í• ìˆ˜ 있습니다" -#: src/view/screens/Profile.tsx:182 +#: src/view/screens/Profile.tsx:190 msgid "Media" msgstr "미디어" @@ -2243,31 +2285,39 @@ msgstr "멘션한 사용ìž" msgid "Menu" msgstr "메뉴" -#: src/view/com/posts/FeedErrorMessage.tsx:197 +#: src/view/com/posts/FeedErrorMessage.tsx:192 msgid "Message from server: {0}" msgstr "서버ì—서 보낸 메시지: {0}" -#: src/Navigation.tsx:117 -#: src/view/screens/Moderation.tsx:66 -#: src/view/screens/Settings/index.tsx:625 +#: src/lib/moderation/useReportOptions.ts:45 +msgid "Misleading Account" +msgstr "ì˜¤í•´ì˜ ì†Œì§€ê°€ 있는 ê³„ì •" + +#: src/Navigation.tsx:119 +#: src/screens/Moderation/index.tsx:106 +#: src/view/screens/Settings/index.tsx:643 #: src/view/shell/desktop/LeftNav.tsx:397 #: src/view/shell/Drawer.tsx:511 #: src/view/shell/Drawer.tsx:512 msgid "Moderation" msgstr "ê²€í† " +#: src/components/moderation/ModerationDetailsDialog.tsx:113 +msgid "Moderation details" +msgstr "ê²€í† ì„¸ë¶€ ì •ë³´" + #: src/view/com/lists/ListCard.tsx:93 #: src/view/com/modals/UserAddRemoveLists.tsx:206 msgid "Moderation list by {0}" msgstr "{0} ë‹˜ì˜ ê²€í† ë¦¬ìŠ¤íŠ¸" -#: src/view/screens/ProfileList.tsx:775 +#: src/view/screens/ProfileList.tsx:789 msgid "Moderation list by <0/>" msgstr "<0/> ë‹˜ì˜ ê²€í† ë¦¬ìŠ¤íŠ¸" #: src/view/com/lists/ListCard.tsx:91 #: src/view/com/modals/UserAddRemoveLists.tsx:204 -#: src/view/screens/ProfileList.tsx:773 +#: src/view/screens/ProfileList.tsx:787 msgid "Moderation list by you" msgstr "ë‚´ ê²€í† ë¦¬ìŠ¤íŠ¸" @@ -2279,96 +2329,93 @@ msgstr "ê²€í† ë¦¬ìŠ¤íŠ¸ ìƒì„±ë¨" msgid "Moderation list updated" msgstr "ê²€í† ë¦¬ìŠ¤íŠ¸ ì—…ë°ì´íЏë¨" -#: src/view/screens/Moderation.tsx:114 +#: src/screens/Moderation/index.tsx:246 msgid "Moderation lists" msgstr "ê²€í† ë¦¬ìŠ¤íŠ¸" -#: src/Navigation.tsx:122 +#: src/Navigation.tsx:124 #: src/view/screens/ModerationModlists.tsx:58 msgid "Moderation Lists" msgstr "ê²€í† ë¦¬ìŠ¤íŠ¸" -#: src/view/screens/Settings/index.tsx:619 +#: src/view/screens/Settings/index.tsx:637 msgid "Moderation settings" msgstr "ê²€í† ì„¤ì •" -#: src/view/com/modals/ModerationDetails.tsx:35 +#: src/Navigation.tsx:216 +msgid "Moderation states" +msgstr "ê²€í† ìƒíƒœ" + +#: src/screens/Moderation/index.tsx:218 +msgid "Moderation tools" +msgstr "ê²€í† ë„구" + +#: src/components/moderation/ModerationDetailsDialog.tsx:49 +#: src/lib/moderation/useModerationCauseDescription.ts:40 msgid "Moderator has chosen to set a general warning on the content." -msgstr "중재ìžê°€ 콘í…ì¸ ì— ì¼ë°˜ ê²½ê³ ë¥¼ ì„¤ì •í–ˆìŠµë‹ˆë‹¤." +msgstr "관리ìžê°€ 콘í…ì¸ ì— ì¼ë°˜ ê²½ê³ ë¥¼ ì„¤ì •í–ˆìŠµë‹ˆë‹¤." #: src/view/shell/desktop/Feeds.tsx:65 msgid "More feeds" msgstr "피드 ë” ë³´ê¸°" -#: src/view/com/profile/ProfileHeader.tsx:523 -#: src/view/screens/ProfileFeed.tsx:363 -#: src/view/screens/ProfileList.tsx:617 +#: src/view/screens/ProfileList.tsx:599 msgid "More options" msgstr "옵션 ë” ë³´ê¸°" -#: src/view/com/util/forms/PostDropdownBtn.tsx:315 -#~ msgid "More post options" -#~ msgstr "게시물 옵션 ë” ë³´ê¸°" - #: src/view/screens/PreferencesThreads.tsx:82 msgid "Most-liked replies first" msgstr "좋아요 ë§Žì€ ìˆœ" #: src/view/com/auth/create/Step2.tsx:122 msgid "Must be at least 3 characters" -msgstr "" +msgstr "최소 3ìž ì´ìƒì´ì–´ì•¼ 합니다" #: src/components/TagMenu/index.tsx:249 msgid "Mute" -msgstr "" +msgstr "뮤트" #: src/components/TagMenu/index.web.tsx:105 msgid "Mute {truncatedTag}" -msgstr "" +msgstr "{truncatedTag} 뮤트" -#: src/view/com/profile/ProfileHeader.tsx:327 +#: src/view/com/profile/ProfileMenu.tsx:279 +#: src/view/com/profile/ProfileMenu.tsx:286 msgid "Mute Account" msgstr "ê³„ì • 뮤트" -#: src/view/screens/ProfileList.tsx:544 +#: src/view/screens/ProfileList.tsx:518 msgid "Mute accounts" msgstr "ê³„ì • 뮤트" #: src/components/TagMenu/index.tsx:209 msgid "Mute all {displayTag} posts" -msgstr "" - -#: src/components/TagMenu/index.tsx:211 -#~ msgid "Mute all {tag} posts" -#~ msgstr "" +msgstr "ëª¨ë“ {displayTag} 게시물 뮤트" #: src/components/dialogs/MutedWords.tsx:149 msgid "Mute in tags only" -msgstr "" +msgstr "태그ì—서만 뮤트" #: src/components/dialogs/MutedWords.tsx:134 msgid "Mute in text & tags" -msgstr "" +msgstr "글 ë° íƒœê·¸ì—서 뮤트" -#: src/view/screens/ProfileList.tsx:491 +#: src/view/screens/ProfileList.tsx:461 +#: src/view/screens/ProfileList.tsx:624 msgid "Mute list" msgstr "리스트 뮤트" -#: src/view/screens/ProfileList.tsx:275 +#: src/view/screens/ProfileList.tsx:619 msgid "Mute these accounts?" msgstr "ì´ ê³„ì •ë“¤ì„ ë®¤íŠ¸í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" -#: src/view/screens/ProfileList.tsx:279 -msgid "Mute this List" -msgstr "ì´ ë¦¬ìŠ¤íŠ¸ 뮤트" - #: src/components/dialogs/MutedWords.tsx:127 msgid "Mute this word in post text and tags" -msgstr "" +msgstr "게시물 글 ë° íƒœê·¸ì—서 ì´ ë‹¨ì–´ 뮤트하기" #: src/components/dialogs/MutedWords.tsx:142 msgid "Mute this word in tags only" -msgstr "" +msgstr "태그ì—서만 ì´ ë‹¨ì–´ 뮤트하기" #: src/view/com/util/forms/PostDropdownBtn.tsx:251 #: src/view/com/util/forms/PostDropdownBtn.tsx:257 @@ -2378,17 +2425,17 @@ msgstr "ìŠ¤ë ˆë“œ 뮤트" #: src/view/com/util/forms/PostDropdownBtn.tsx:267 #: src/view/com/util/forms/PostDropdownBtn.tsx:269 msgid "Mute words & tags" -msgstr "" +msgstr "단어 ë° íƒœê·¸ 뮤트" #: src/view/com/lists/ListCard.tsx:102 msgid "Muted" msgstr "뮤트ë¨" -#: src/view/screens/Moderation.tsx:128 +#: src/screens/Moderation/index.tsx:258 msgid "Muted accounts" msgstr "뮤트한 ê³„ì •" -#: src/Navigation.tsx:127 +#: src/Navigation.tsx:129 #: src/view/screens/ModerationMutedAccounts.tsx:107 msgid "Muted Accounts" msgstr "뮤트한 ê³„ì •" @@ -2397,15 +2444,20 @@ msgstr "뮤트한 ê³„ì •" msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private." msgstr "ê³„ì •ì„ ë®¤íŠ¸í•˜ë©´ 피드와 알림ì—서 해당 ê³„ì •ì˜ ê²Œì‹œë¬¼ì´ ì‚¬ë¼ì§‘니다. 뮤트 목ë¡ì€ ì™„ì „ížˆ 비공개로 ìœ ì§€ë©ë‹ˆë‹¤." -#: src/view/screens/Moderation.tsx:100 +#: src/lib/moderation/useModerationCauseDescription.ts:85 +msgid "Muted by \"{0}\"" +msgstr "\"{0}\" ë‹˜ì´ ë®¤íŠ¸í•¨" + +#: src/screens/Moderation/index.tsx:234 msgid "Muted words & tags" -msgstr "" +msgstr "뮤트한 단어 ë° íƒœê·¸" -#: src/view/screens/ProfileList.tsx:277 +#: src/view/screens/ProfileList.tsx:621 msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." msgstr "뮤트 목ë¡ì€ 비공개입니다. 뮤트한 ê³„ì •ì€ ë‚˜ì™€ ìƒí˜¸ìž‘ìš©í• ìˆ˜ 있지만 해당 ê³„ì •ì˜ ê²Œì‹œë¬¼ì„ ë³´ê±°ë‚˜ 해당 ê³„ì •ìœ¼ë¡œë¶€í„° ì•Œë¦¼ì„ ë°›ì„ ìˆ˜ 없습니다." -#: src/view/com/modals/BirthDateSettings.tsx:56 +#: src/components/dialogs/BirthDateSettings.tsx:34 +#: src/components/dialogs/BirthDateSettings.tsx:86 msgid "My Birthday" msgstr "ë‚´ ìƒë…„ì›”ì¼" @@ -2417,13 +2469,13 @@ msgstr "ë‚´ 피드" msgid "My Profile" msgstr "ë‚´ 프로필" -#: src/view/screens/Settings/index.tsx:582 +#: src/view/screens/Settings/index.tsx:600 msgid "My Saved Feeds" msgstr "ë‚´ ì €ìž¥ëœ í”¼ë“œ" #: src/view/com/auth/server-input/index.tsx:118 msgid "my-server.com" -msgstr "" +msgstr "my-server.com" #: src/view/com/modals/AddAppPasswords.tsx:179 #: src/view/com/modals/CreateOrEditList.tsx:290 @@ -2434,6 +2486,12 @@ msgstr "ì´ë¦„" msgid "Name is required" msgstr "ì´ë¦„ì„ ìž…ë ¥í•˜ì„¸ìš”" +#: src/lib/moderation/useReportOptions.ts:57 +#: src/lib/moderation/useReportOptions.ts:78 +#: src/lib/moderation/useReportOptions.ts:86 +msgid "Name or Description Violates Community Standards" +msgstr "ì´ë¦„ ë˜ëŠ” ì„¤ëª…ì´ ì»¤ë®¤ë‹ˆí‹° ê¸°ì¤€ì„ ìœ„ë°˜í•¨" + #: src/screens/Onboarding/index.tsx:25 msgid "Nature" msgstr "ìžì—°" @@ -2450,6 +2508,10 @@ msgstr "ë‹¤ìŒ í™”ë©´ìœ¼ë¡œ ì´ë™í•©ë‹ˆë‹¤" msgid "Navigates to your profile" msgstr "ë‚´ 프로필로 ì´ë™í•©ë‹ˆë‹¤" +#: src/components/ReportDialog/SelectReportOptionView.tsx:123 +msgid "Need to report a copyright violation?" +msgstr "ì €ìž‘ê¶Œ ìœ„ë°˜ì„ ì‹ ê³ í•´ì•¼ 하나요?" + #: src/view/com/modals/EmbedConsent.tsx:107 #: src/view/com/modals/EmbedConsent.tsx:123 msgid "Never load embeds from {0}" @@ -2458,15 +2520,11 @@ msgstr "{0}ì—서 ìž„ë² ë“œë¥¼ 불러오지 않습니다" #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:72 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:72 msgid "Never lose access to your followers and data." -msgstr "팔로워와 ë°ì´í„°ì— 대한 ì ‘ê·¼ ê¶Œí•œì„ ìžƒì§€ 않습니다." +msgstr "팔로워와 ë°ì´í„°ì— 대한 ì ‘ê·¼ ê¶Œí•œì„ ìžƒì§€ 마세요." #: src/screens/Onboarding/StepFinished.tsx:119 msgid "Never lose access to your followers or data." -msgstr "팔로워 ë˜ëŠ” ë°ì´í„°ì— 대한 ì ‘ê·¼ ê¶Œí•œì„ ìžƒì§€ 않습니다." - -#: src/components/dialogs/MutedWords.tsx:293 -msgid "Nevermind" -msgstr "" +msgstr "팔로워 ë˜ëŠ” ë°ì´í„°ì— 대한 ì ‘ê·¼ ê¶Œí•œì„ ìžƒì§€ 마세요." #: src/view/screens/Lists.tsx:76 msgctxt "action" @@ -2487,7 +2545,7 @@ msgstr "새 비밀번호" #: src/view/com/modals/ChangePassword.tsx:215 msgid "New Password" -msgstr "" +msgstr "새 비밀번호" #: src/view/com/feeds/FeedPage.tsx:126 msgctxt "action" @@ -2496,10 +2554,10 @@ msgstr "새 게시물" #: src/view/screens/Feeds.tsx:555 #: src/view/screens/Notifications.tsx:168 -#: src/view/screens/Profile.tsx:382 +#: src/view/screens/Profile.tsx:450 #: src/view/screens/ProfileFeed.tsx:433 -#: src/view/screens/ProfileList.tsx:196 -#: src/view/screens/ProfileList.tsx:224 +#: src/view/screens/ProfileList.tsx:199 +#: src/view/screens/ProfileList.tsx:227 #: src/view/shell/desktop/LeftNav.tsx:248 msgid "New post" msgstr "새 게시물" @@ -2551,12 +2609,12 @@ msgstr "ë‹¤ìŒ ì´ë¯¸ì§€" msgid "No" msgstr "아니요" -#: src/view/screens/ProfileFeed.tsx:584 -#: src/view/screens/ProfileList.tsx:755 +#: src/view/screens/ProfileFeed.tsx:561 +#: src/view/screens/ProfileList.tsx:769 msgid "No description" msgstr "설명 ì—†ìŒ" -#: src/view/com/profile/ProfileHeader.tsx:170 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:111 msgid "No longer following {0}" msgstr "ë” ì´ìƒ {0} ë‹˜ì„ íŒ”ë¡œìš°í•˜ì§€ 않ìŒ" @@ -2569,9 +2627,9 @@ msgstr "ì•„ì§ ì•Œë¦¼ì´ ì—†ìŠµë‹ˆë‹¤." msgid "No result" msgstr "ê²°ê³¼ ì—†ìŒ" -#: src/components/Lists.tsx:192 +#: src/components/Lists.tsx:191 msgid "No results found" -msgstr "" +msgstr "결과를 ì°¾ì„ ìˆ˜ ì—†ìŒ" #: src/view/screens/Feeds.tsx:495 msgid "No results found for \"{query}\"" @@ -2591,12 +2649,21 @@ msgstr "괜찮습니다" msgid "Nobody" msgstr "ì—†ìŒ" +#: src/components/LikedByList.tsx:102 +#: 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/view/com/modals/SelfLabel.tsx:135 msgid "Not Applicable." msgstr "해당 ì—†ìŒ." -#: src/Navigation.tsx:107 -#: src/view/screens/Profile.tsx:106 +#: src/Navigation.tsx:109 +#: src/view/screens/Profile.tsx:97 msgid "Not Found" msgstr "ì°¾ì„ ìˆ˜ ì—†ìŒ" @@ -2605,14 +2672,19 @@ msgstr "ì°¾ì„ ìˆ˜ ì—†ìŒ" msgid "Not right now" msgstr "ë‚˜ì¤‘ì— í•˜ê¸°" -#: src/view/screens/Moderation.tsx:252 +#: src/view/com/profile/ProfileMenu.tsx:368 +#: src/view/com/util/forms/PostDropdownBtn.tsx:342 +msgid "Note about sharing" +msgstr "ê³µìœ ê´€ë ¨ ì°¸ê³ ì‚¬í•" + +#: src/screens/Moderation/index.tsx:546 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/Navigation.tsx:457 +#: src/Navigation.tsx:469 #: src/view/screens/Notifications.tsx:124 #: src/view/screens/Notifications.tsx:148 -#: src/view/shell/bottom-bar/BottomBar.tsx:205 +#: src/view/shell/bottom-bar/BottomBar.tsx:207 #: src/view/shell/desktop/LeftNav.tsx:361 #: src/view/shell/Drawer.tsx:435 #: src/view/shell/Drawer.tsx:436 @@ -2623,9 +2695,17 @@ msgstr "알림" msgid "Nudity" msgstr "노출" -#: src/view/com/util/ErrorBoundary.tsx:35 +#: src/lib/moderation/useReportOptions.ts:71 +msgid "Nudity or pornography not labeled as such" +msgstr "누드 ë˜ëŠ” ìŒëž€ë¬¼ë¡œ ì„¤ì •ë˜ì§€ ì•Šì€ ì½˜í…ì¸ " + +#: src/lib/moderation/useLabelBehaviorDescription.ts:11 +msgid "Off" +msgstr "ë„기" + +#: src/view/com/util/ErrorBoundary.tsx:49 msgid "Oh no!" -msgstr "안 ë¼!" +msgstr "ì´ëŸ°!" #: src/screens/Onboarding/StepInterests/index.tsx:128 msgid "Oh no! Something went wrong." @@ -2639,11 +2719,11 @@ msgstr "확ì¸" msgid "Oldest replies first" msgstr "ì˜¤ëž˜ëœ ìˆœ" -#: src/view/screens/Settings/index.tsx:234 +#: src/view/screens/Settings/index.tsx:240 msgid "Onboarding reset" msgstr "온보딩 ìž¬ì„¤ì •" -#: src/view/com/composer/Composer.tsx:382 +#: src/view/com/composer/Composer.tsx:391 msgid "One or more images is missing alt text." msgstr "하나 ì´ìƒì˜ ì´ë¯¸ì§€ì— 대체 í…스트가 누ë½ë˜ì—ˆìŠµë‹ˆë‹¤." @@ -2651,13 +2731,13 @@ msgstr "하나 ì´ìƒì˜ ì´ë¯¸ì§€ì— 대체 í…스트가 누ë½ë˜ì—ˆìŠµë‹ˆë‹¤. msgid "Only {0} can reply." msgstr "{0}ë§Œ ë‹µê¸€ì„ ë‹¬ 수 있습니다." -#: src/components/Lists.tsx:82 +#: src/components/Lists.tsx:81 msgid "Oops, something went wrong!" -msgstr "" +msgstr "ì´ëŸ°, ë”ê°€ 잘못ë˜ì—ˆìŠµë‹ˆë‹¤!" -#: src/components/Lists.tsx:188 -#: src/view/screens/AppPasswords.tsx:65 -#: src/view/screens/Profile.tsx:106 +#: src/components/Lists.tsx:187 +#: src/view/screens/AppPasswords.tsx:67 +#: src/view/screens/Profile.tsx:97 msgid "Oops!" msgstr "ì´ëŸ°!" @@ -2665,32 +2745,33 @@ msgstr "ì´ëŸ°!" msgid "Open" msgstr "공개성" -#: src/view/screens/Moderation.tsx:75 -msgid "Open content filtering settings" -msgstr "" - -#: src/view/com/composer/Composer.tsx:477 -#: src/view/com/composer/Composer.tsx:478 +#: src/view/com/composer/Composer.tsx:486 +#: src/view/com/composer/Composer.tsx:487 msgid "Open emoji picker" msgstr "ì´ëª¨í‹°ì½˜ ì„ íƒê¸° 열기" -#: src/view/screens/Settings/index.tsx:712 +#: src/view/screens/ProfileFeed.tsx:299 +msgid "Open feed options menu" +msgstr "피드 옵션 메뉴 열기" + +#: src/view/screens/Settings/index.tsx:730 msgid "Open links with in-app browser" -msgstr "ë§í¬ë¥¼ ì¸ì•± 브ë¼ìš°ì €ë¡œ 엽니다" +msgstr "ë§í¬ë¥¼ ì¸ì•± 브ë¼ìš°ì €ë¡œ 열기" -#: src/view/screens/Moderation.tsx:92 -msgid "Open muted words settings" -msgstr "" +#: src/screens/Moderation/index.tsx:230 +msgid "Open muted words and tags settings" +msgstr "뮤트한 단어 ë° íƒœê·¸ ì„¤ì • 열기" #: src/view/com/home/HomeHeaderLayoutMobile.tsx:50 msgid "Open navigation" msgstr "내비게ì´ì…˜ 열기" -#: src/view/com/util/forms/PostDropdownBtn.tsx:175 +#: src/view/com/util/forms/PostDropdownBtn.tsx:183 msgid "Open post options menu" -msgstr "" +msgstr "게시물 옵션 메뉴 열기" -#: src/view/screens/Settings/index.tsx:804 +#: src/view/screens/Settings/index.tsx:822 +#: src/view/screens/Settings/index.tsx:832 msgid "Open storybook page" msgstr "ìŠ¤í† ë¦¬ë¶ íŽ˜ì´ì§€ 열기" @@ -2702,11 +2783,11 @@ msgstr "{numItems}번째 ì˜µì…˜ì„ ì—½ë‹ˆë‹¤" msgid "Opens additional details for a debug entry" msgstr "디버그 í•ëª©ì— ëŒ€í•œ 추가 세부 ì •ë³´ë¥¼ 엽니다" -#: src/view/com/notifications/FeedItem.tsx:349 +#: src/view/com/notifications/FeedItem.tsx:348 msgid "Opens an expanded list of users in this notification" msgstr "ì´ ì•Œë¦¼ì—서 í™•ìž¥ëœ ì‚¬ìš©ìž ëª©ë¡ì„ 엽니다" -#: src/view/com/composer/photos/OpenCameraBtn.tsx:61 +#: src/view/com/composer/photos/OpenCameraBtn.tsx:78 msgid "Opens camera on device" msgstr "기기ì—서 ì¹´ë©”ë¼ë¥¼ 엽니다" @@ -2714,7 +2795,7 @@ msgstr "기기ì—서 ì¹´ë©”ë¼ë¥¼ 엽니다" msgid "Opens composer" msgstr "답글 작성 ìƒìžë¥¼ 엽니다" -#: src/view/screens/Settings/index.tsx:595 +#: src/view/screens/Settings/index.tsx:613 msgid "Opens configurable language settings" msgstr "구성 가능한 언어 ì„¤ì •ì„ ì—½ë‹ˆë‹¤" @@ -2722,31 +2803,15 @@ msgstr "구성 가능한 언어 ì„¤ì •ì„ ì—½ë‹ˆë‹¤" msgid "Opens device photo gallery" msgstr "ê¸°ê¸°ì˜ ì‚¬ì§„ 갤러리를 엽니다" -#: src/view/com/profile/ProfileHeader.tsx:420 -msgid "Opens editor for profile display name, avatar, background image, and description" -msgstr "프로필 표시 ì´ë¦„, 아바타, ë°°ê²½ ì´ë¯¸ì§€ ë° ì„¤ëª… 편집기를 엽니다" - -#: src/view/screens/Settings/index.tsx:649 +#: src/view/screens/Settings/index.tsx:667 msgid "Opens external embeds settings" msgstr "외부 ìž„ë² ë“œ ì„¤ì •ì„ ì—½ë‹ˆë‹¤" -#: src/view/com/profile/ProfileHeader.tsx:575 -msgid "Opens followers list" -msgstr "팔로워 목ë¡ì„ 엽니다" - -#: src/view/com/profile/ProfileHeader.tsx:594 -msgid "Opens following list" -msgstr "팔로우 중 목ë¡ì„ 엽니다" - -#: src/view/screens/Settings.tsx:412 -#~ msgid "Opens invite code list" -#~ msgstr "초대 코드 목ë¡ì„ 엽니다" - #: src/view/com/modals/InviteCodes.tsx:172 msgid "Opens list of invite codes" msgstr "초대 코드 목ë¡ì„ 엽니다" -#: src/view/screens/Settings/index.tsx:774 +#: src/view/screens/Settings/index.tsx:792 msgid "Opens modal for account deletion confirmation. Requires email code." msgstr "ê³„ì • ì‚ì œ 확ì¸ì„ 위한 대화 ìƒìžë¥¼ 엽니다. ì´ë©”ì¼ ì½”ë“œê°€ 필요합니다" @@ -2754,7 +2819,7 @@ msgstr "ê³„ì • ì‚ì œ 확ì¸ì„ 위한 대화 ìƒìžë¥¼ 엽니다. ì´ë©”ì¼ ì½” msgid "Opens modal for using custom domain" msgstr "ì‚¬ìš©ìž ì§€ì • ë„ë©”ì¸ì„ 사용하기 위한 대화 ìƒìžë¥¼ 엽니다" -#: src/view/screens/Settings/index.tsx:620 +#: src/view/screens/Settings/index.tsx:638 msgid "Opens moderation settings" msgstr "ê²€í† ì„¤ì •ì„ ì—½ë‹ˆë‹¤" @@ -2767,27 +2832,28 @@ msgstr "비밀번호 ìž¬ì„¤ì • ì–‘ì‹ì„ 엽니다" msgid "Opens screen to edit Saved Feeds" msgstr "ì €ìž¥ëœ í”¼ë“œë¥¼ íŽ¸ì§‘í• ìˆ˜ 있는 í™”ë©´ì„ ì—½ë‹ˆë‹¤" -#: src/view/screens/Settings/index.tsx:576 +#: src/view/screens/Settings/index.tsx:594 msgid "Opens screen with all saved feeds" msgstr "ëª¨ë“ ì €ìž¥ëœ í”¼ë“œ í™”ë©´ì„ ì—½ë‹ˆë‹¤" -#: src/view/screens/Settings/index.tsx:676 +#: src/view/screens/Settings/index.tsx:694 msgid "Opens the app password settings page" msgstr "비밀번호 ì„¤ì • 페ì´ì§€ë¥¼ 엽니다" -#: src/view/screens/Settings/index.tsx:535 +#: src/view/screens/Settings/index.tsx:553 msgid "Opens the home feed preferences" msgstr "홈 피드 ì„¤ì •ì„ ì—½ë‹ˆë‹¤" -#: src/view/screens/Settings/index.tsx:805 +#: src/view/screens/Settings/index.tsx:823 +#: src/view/screens/Settings/index.tsx:833 msgid "Opens the storybook page" msgstr "ìŠ¤í† ë¦¬ë¶ íŽ˜ì´ì§€ë¥¼ 엽니다" -#: src/view/screens/Settings/index.tsx:793 +#: src/view/screens/Settings/index.tsx:811 msgid "Opens the system log page" msgstr "시스템 로그 페ì´ì§€ë¥¼ 엽니다" -#: src/view/screens/Settings/index.tsx:556 +#: src/view/screens/Settings/index.tsx:574 msgid "Opens the threads preferences" msgstr "ìŠ¤ë ˆë“œ ì„¤ì •ì„ ì—½ë‹ˆë‹¤" @@ -2795,23 +2861,27 @@ msgstr "ìŠ¤ë ˆë“œ ì„¤ì •ì„ ì—½ë‹ˆë‹¤" msgid "Option {0} of {numItems}" msgstr "{numItems}ê°œ 중 {0}번째 옵션" +#: src/components/ReportDialog/SubmitView.tsx:162 +msgid "Optionally provide additional information below:" +msgstr "ì„ íƒ ì‚¬í•으로 ì•„ëž˜ì— ì¶”ê°€ ì •ë³´ë¥¼ ìž…ë ¥í•©ë‹ˆë‹¤:" + #: src/view/com/modals/Threadgate.tsx:89 msgid "Or combine these options:" msgstr "ë˜ëŠ” ë‹¤ìŒ ì˜µì…˜ì„ ê²°í•©í•˜ì„¸ìš”:" +#: src/lib/moderation/useReportOptions.ts:25 +msgid "Other" +msgstr "기타" + #: src/view/com/auth/login/ChooseAccountForm.tsx:138 msgid "Other account" msgstr "다른 ê³„ì •" -#: src/view/com/modals/ServerInput.tsx:88 -#~ msgid "Other service" -#~ msgstr "다른 서비스" - #: src/view/com/composer/select-language/SelectLangBtn.tsx:91 msgid "Other..." msgstr "기타…" -#: src/components/Lists.tsx:194 +#: src/components/Lists.tsx:193 #: src/view/screens/NotFound.tsx:45 msgid "Page not found" msgstr "페ì´ì§€ë¥¼ ì°¾ì„ ìˆ˜ ì—†ìŒ" @@ -2836,11 +2906,11 @@ msgstr "비밀번호 변경ë¨" msgid "Password updated!" msgstr "비밀번호 변경ë¨" -#: src/Navigation.tsx:162 +#: src/Navigation.tsx:164 msgid "People followed by @{0}" msgstr "@{0} ë‹˜ì´ íŒ”ë¡œìš°í•œ 사람들" -#: src/Navigation.tsx:155 +#: src/Navigation.tsx:157 msgid "People following @{0}" msgstr "@{0} ë‹˜ì„ íŒ”ë¡œìš°í•˜ëŠ” 사람들" @@ -2856,19 +2926,19 @@ msgstr "ì•¨ë²”ì— ì ‘ê·¼í• ìˆ˜ 있는 ê¶Œí•œì´ ê±°ë¶€ë˜ì—ˆìŠµë‹ˆë‹¤. ì‹œìŠ¤í… msgid "Pets" msgstr "ë°˜ë ¤ë™ë¬¼" -#: src/view/com/auth/create/Step2.tsx:183 -#~ msgid "Phone number" -#~ msgstr "ì „í™”ë²ˆí˜¸" - #: src/view/com/modals/SelfLabel.tsx:121 msgid "Pictures meant for adults." msgstr "성ì¸ìš© 사진." -#: src/view/screens/ProfileFeed.tsx:354 -#: src/view/screens/ProfileList.tsx:581 +#: src/view/screens/ProfileFeed.tsx:291 +#: src/view/screens/ProfileList.tsx:563 msgid "Pin to home" msgstr "í™ˆì— ê³ ì •" +#: src/view/screens/ProfileFeed.tsx:294 +msgid "Pin to Home" +msgstr "í™ˆì— ê³ ì •" + #: src/view/screens/SavedFeeds.tsx:88 msgid "Pinned Feeds" msgstr "ê³ ì •ëœ í”¼ë“œ" @@ -2896,7 +2966,7 @@ msgstr "비밀번호를 ìž…ë ¥í•˜ì„¸ìš”." #: src/view/com/auth/create/state.ts:131 msgid "Please complete the verification captcha." -msgstr "" +msgstr "ì¸ì¦ 캡차를 완료해 주세요." #: src/view/com/modals/ChangeEmail.tsx:67 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." @@ -2906,25 +2976,13 @@ msgstr "ì´ë©”ì¼ì„ 변경하기 ì „ì— ì´ë©”ì¼ì„ 확ì¸í•´ 주세요. ì´ëŠ msgid "Please enter a name for your app password. All spaces is not allowed." msgstr "앱 ë¹„ë°€ë²ˆí˜¸ì˜ ì´ë¦„ì„ ìž…ë ¥í•˜ì„¸ìš”. ëª¨ë“ ê³µë°± 문ìžëŠ” 허용ë˜ì§€ 않습니다." -#: src/view/com/auth/create/Step2.tsx:206 -#~ msgid "Please enter a phone number that can receive SMS text messages." -#~ msgstr "SMS ë¬¸ìž ë©”ì‹œì§€ë¥¼ ë°›ì„ ìˆ˜ 있는 íœ´ëŒ€í° ë²ˆí˜¸ë¥¼ ìž…ë ¥í•˜ì„¸ìš”." - #: src/view/com/modals/AddAppPasswords.tsx:145 msgid "Please enter a unique name for this App Password or use our randomly generated one." msgstr "ì´ ì•± ë¹„ë°€ë²ˆí˜¸ì— ëŒ€í•´ ê³ ìœ í•œ ì´ë¦„ì„ ìž…ë ¥í•˜ê±°ë‚˜ 무작위로 ìƒì„±ëœ ì´ë¦„ì„ ì‚¬ìš©í•©ë‹ˆë‹¤." #: src/components/dialogs/MutedWords.tsx:68 msgid "Please enter a valid word, tag, or phrase to mute" -msgstr "" - -#: src/view/com/auth/create/state.ts:170 -#~ msgid "Please enter the code you received by SMS." -#~ msgstr "SMS로 ë°›ì€ ì½”ë“œë¥¼ ìž…ë ¥í•˜ì„¸ìš”." - -#: src/view/com/auth/create/Step2.tsx:282 -#~ msgid "Please enter the verification code sent to {phoneNumberFormatted}." -#~ msgstr "{phoneNumberFormatted}(으)로 ë³´ë‚´ì§„ ì¸ì¦ 코드를 ìž…ë ¥í•˜ì„¸ìš”." +msgstr "ë®¤íŠ¸í• ë‹¨ì–´ë‚˜ 태그 ë˜ëŠ” 문구를 ìž…ë ¥í•˜ì„¸ìš”" #: src/view/com/auth/create/state.ts:103 msgid "Please enter your email." @@ -2934,16 +2992,15 @@ msgstr "ì´ë©”ì¼ì„ ìž…ë ¥í•˜ì„¸ìš”." msgid "Please enter your password as well:" msgstr "ë¹„ë°€ë²ˆí˜¸ë„ ìž…ë ¥í•´ 주세요:" -#: src/view/com/modals/AppealLabel.tsx:72 -#: src/view/com/modals/AppealLabel.tsx:75 -msgid "Please tell us why you think this content warning was incorrectly applied!" -msgstr "ì´ ì½˜í…ì¸ ê²½ê³ ê°€ 잘못 ì ìš©ë˜ì—ˆë‹¤ê³ ìƒê°í•˜ëŠ” ì´ìœ 를 ì•Œë ¤ì£¼ì„¸ìš”!" +#: src/components/moderation/LabelsOnMeDialog.tsx:222 +msgid "Please explain why you think this label was incorrectly applied by {0}" +msgstr "{0}ì´(ê°€) ì´ ë¼ë²¨ì„ 잘못 ì ìš©í–ˆë‹¤ê³ ìƒê°í•˜ëŠ” ì´ìœ 를 설명해 주세요" #: src/view/com/modals/VerifyEmail.tsx:101 msgid "Please Verify Your Email" msgstr "ì´ë©”ì¼ ì¸ì¦í•˜ê¸°" -#: src/view/com/composer/Composer.tsx:222 +#: src/view/com/composer/Composer.tsx:221 msgid "Please wait for your link card to finish loading" msgstr "ë§í¬ 카드를 ì™„ì „ížˆ 불러올 때까지 ê¸°ë‹¤ë ¤ì£¼ì„¸ìš”" @@ -2953,15 +3010,19 @@ msgstr "ì •ì¹˜" #: src/view/com/modals/SelfLabel.tsx:111 msgid "Porn" -msgstr "í¬ë¥´ë…¸" +msgstr "ìŒëž€ë¬¼" -#: src/view/com/composer/Composer.tsx:357 -#: src/view/com/composer/Composer.tsx:365 +#: src/lib/moderation/useGlobalLabelStrings.ts:34 +msgid "Pornography" +msgstr "ìŒëž€ë¬¼" + +#: src/view/com/composer/Composer.tsx:366 +#: src/view/com/composer/Composer.tsx:374 msgctxt "action" msgid "Post" msgstr "게시하기" -#: src/view/com/post-thread/PostThread.tsx:303 +#: src/view/com/post-thread/PostThread.tsx:304 msgctxt "description" msgid "Post" msgstr "게시물" @@ -2970,20 +3031,30 @@ msgstr "게시물" msgid "Post by {0}" msgstr "{0} ë‹˜ì˜ ê²Œì‹œë¬¼" -#: src/Navigation.tsx:174 -#: src/Navigation.tsx:181 -#: src/Navigation.tsx:188 +#: src/Navigation.tsx:176 +#: src/Navigation.tsx:183 +#: src/Navigation.tsx:190 msgid "Post by @{0}" msgstr "@{0} ë‹˜ì˜ ê²Œì‹œë¬¼" -#: src/view/com/util/forms/PostDropdownBtn.tsx:108 +#: src/view/com/util/forms/PostDropdownBtn.tsx:105 msgid "Post deleted" msgstr "게시물 ì‚ì œë¨" -#: src/view/com/post-thread/PostThread.tsx:462 +#: src/view/com/post-thread/PostThread.tsx:463 msgid "Post hidden" msgstr "게시물 숨김" +#: src/components/moderation/ModerationDetailsDialog.tsx:98 +#: src/lib/moderation/useModerationCauseDescription.ts:99 +msgid "Post Hidden by Muted Word" +msgstr "뮤트한 단어로 숨겨진 게시물" + +#: src/components/moderation/ModerationDetailsDialog.tsx:101 +#: src/lib/moderation/useModerationCauseDescription.ts:108 +msgid "Post Hidden by You" +msgstr "ë‚´ê°€ 숨긴 게시물" + #: src/view/com/composer/select-language/SelectLangBtn.tsx:87 msgid "Post language" msgstr "게시물 언어" @@ -2992,21 +3063,21 @@ msgstr "게시물 언어" msgid "Post Languages" msgstr "게시물 언어" -#: src/view/com/post-thread/PostThread.tsx:514 +#: src/view/com/post-thread/PostThread.tsx:515 msgid "Post not found" msgstr "ê²Œì‹œë¬¼ì„ ì°¾ì„ ìˆ˜ ì—†ìŒ" #: src/components/TagMenu/index.tsx:253 msgid "posts" -msgstr "" +msgstr "게시물" -#: src/view/screens/Profile.tsx:180 +#: src/view/screens/Profile.tsx:188 msgid "Posts" msgstr "게시물" #: src/components/dialogs/MutedWords.tsx:90 msgid "Posts can be muted based on their text, their tags, or both." -msgstr "" +msgstr "ê²Œì‹œë¬¼ì˜ ê¸€ ë° íƒœê·¸ì— ë”°ë¼ ê²Œì‹œë¬¼ì„ ë®¤íŠ¸í• ìˆ˜ 있습니다." #: src/view/com/posts/FeedErrorMessage.tsx:64 msgid "Posts hidden" @@ -3028,14 +3099,14 @@ msgstr "주 언어" msgid "Prioritize Your Follows" msgstr "ë‚´ 팔로우 ë¨¼ì € 표시" -#: src/view/screens/Settings/index.tsx:632 +#: src/view/screens/Settings/index.tsx:650 #: src/view/shell/desktop/RightNav.tsx:72 msgid "Privacy" msgstr "ê°œì¸ì •ë³´" -#: src/Navigation.tsx:219 +#: src/Navigation.tsx:231 #: src/view/screens/PrivacyPolicy.tsx:29 -#: src/view/screens/Settings/index.tsx:891 +#: src/view/screens/Settings/index.tsx:919 #: src/view/shell/Drawer.tsx:262 msgid "Privacy Policy" msgstr "ê°œì¸ì •ë³´ 처리방침" @@ -3044,7 +3115,12 @@ msgstr "ê°œì¸ì •ë³´ 처리방침" msgid "Processing..." msgstr "처리 중…" -#: src/view/shell/bottom-bar/BottomBar.tsx:247 +#: src/view/screens/DebugMod.tsx:888 +#: src/view/screens/Profile.tsx:340 +msgid "profile" +msgstr "프로필" + +#: src/view/shell/bottom-bar/BottomBar.tsx:249 #: src/view/shell/desktop/LeftNav.tsx:415 #: src/view/shell/Drawer.tsx:70 #: src/view/shell/Drawer.tsx:546 @@ -3056,7 +3132,7 @@ msgstr "프로필" msgid "Profile updated" msgstr "프로필 ì—…ë°ì´íЏë¨" -#: src/view/screens/Settings/index.tsx:949 +#: src/view/screens/Settings/index.tsx:977 msgid "Protect your account by verifying your email." msgstr "ì´ë©”ì¼ì„ ì¸ì¦í•˜ì—¬ ê³„ì •ì„ ë³´í˜¸í•˜ì„¸ìš”." @@ -3072,11 +3148,11 @@ msgstr "ì¼ê´„ 뮤트하거나 ì°¨ë‹¨í• ìˆ˜ 있는 공개ì ì´ê³ ê³µìœ ê°€ëŠ msgid "Public, shareable lists which can drive feeds." msgstr "피드를 íƒìƒ‰í• 수 있는 공개ì ì´ê³ ê³µìœ ê°€ëŠ¥í•œ 목ë¡ìž…니다." -#: src/view/com/composer/Composer.tsx:342 +#: src/view/com/composer/Composer.tsx:351 msgid "Publish post" msgstr "게시물 게시하기" -#: src/view/com/composer/Composer.tsx:342 +#: src/view/com/composer/Composer.tsx:351 msgid "Publish reply" msgstr "답글 게시하기" @@ -3110,36 +3186,46 @@ msgstr "추천 피드" msgid "Recommended Users" msgstr "추천 사용ìž" -#: src/components/dialogs/MutedWords.tsx:298 +#: src/components/dialogs/MutedWords.tsx:287 +#: src/view/com/feeds/FeedSourceCard.tsx:283 #: src/view/com/modals/ListAddRemoveUsers.tsx:264 #: src/view/com/modals/SelfLabel.tsx:83 #: src/view/com/modals/UserAddRemoveLists.tsx:219 -#: src/view/com/util/UserAvatar.tsx:285 -#: src/view/com/util/UserBanner.tsx:91 +#: src/view/com/posts/FeedErrorMessage.tsx:204 msgid "Remove" msgstr "ì œê±°" -#: src/view/com/feeds/FeedSourceCard.tsx:108 -msgid "Remove {0} from my feeds?" -msgstr "{0}ì„(를) ë‚´ 피드ì—서 ì œê±°í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" - #: src/view/com/util/AccountDropdownBtn.tsx:22 msgid "Remove account" msgstr "ê³„ì • ì œê±°" -#: src/view/com/posts/FeedErrorMessage.tsx:131 -#: src/view/com/posts/FeedErrorMessage.tsx:166 +#: src/view/com/util/UserAvatar.tsx:351 +msgid "Remove Avatar" +msgstr "아바타 ì œê±°" + +#: src/view/com/util/UserBanner.tsx:145 +msgid "Remove Banner" +msgstr "배너 ì œê±°" + +#: src/view/com/posts/FeedErrorMessage.tsx:160 msgid "Remove feed" msgstr "피드 ì œê±°" -#: src/view/com/feeds/FeedSourceCard.tsx:107 -#: src/view/com/feeds/FeedSourceCard.tsx:169 -#: src/view/com/feeds/FeedSourceCard.tsx:174 -#: src/view/com/feeds/FeedSourceCard.tsx:245 -#: src/view/screens/ProfileFeed.tsx:273 +#: src/view/com/posts/FeedErrorMessage.tsx:201 +msgid "Remove feed?" +msgstr "피드를 ì œê±°í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" + +#: src/view/com/feeds/FeedSourceCard.tsx:173 +#: src/view/com/feeds/FeedSourceCard.tsx:233 +#: src/view/screens/ProfileFeed.tsx:334 +#: src/view/screens/ProfileFeed.tsx:340 msgid "Remove from my feeds" msgstr "ë‚´ 피드ì—서 ì œê±°" +#: src/view/com/feeds/FeedSourceCard.tsx:278 +msgid "Remove from my feeds?" +msgstr "ë‚´ 피드ì—서 ì œê±°í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" + #: src/view/com/composer/photos/Gallery.tsx:167 msgid "Remove image" msgstr "ì´ë¯¸ì§€ ì œê±°" @@ -3148,37 +3234,36 @@ msgstr "ì´ë¯¸ì§€ ì œê±°" msgid "Remove image preview" msgstr "ì´ë¯¸ì§€ 미리보기 ì œê±°" -#: src/components/dialogs/MutedWords.tsx:343 +#: src/components/dialogs/MutedWords.tsx:330 msgid "Remove mute word from your list" -msgstr "" +msgstr "목ë¡ì—서 뮤트한 단어 ì œê±°" #: src/view/com/modals/Repost.tsx:47 msgid "Remove repost" msgstr "재게시를 취소합니다" -#: src/view/com/feeds/FeedSourceCard.tsx:175 -msgid "Remove this feed from my feeds?" -msgstr "ì´ í”¼ë“œë¥¼ ë‚´ 피드ì—서 ì œê±°í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" - -#: src/view/com/posts/FeedErrorMessage.tsx:132 -msgid "Remove this feed from your saved feeds?" -msgstr "ì´ í”¼ë“œë¥¼ ì €ìž¥ëœ í”¼ë“œì—서 ì œê±°í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" +#: src/view/com/posts/FeedErrorMessage.tsx:202 +msgid "Remove this feed from your saved feeds" +msgstr "ì €ìž¥ëœ í”¼ë“œì—서 ì´ í”¼ë“œë¥¼ ì œê±°í•©ë‹ˆë‹¤" #: src/view/com/modals/ListAddRemoveUsers.tsx:199 #: src/view/com/modals/UserAddRemoveLists.tsx:152 msgid "Removed from list" msgstr "리스트ì—서 ì œê±°ë¨" -#: src/view/com/feeds/FeedSourceCard.tsx:113 -#: src/view/com/feeds/FeedSourceCard.tsx:180 +#: src/view/com/feeds/FeedSourceCard.tsx:121 msgid "Removed from my feeds" msgstr "ë‚´ 피드ì—서 ì œê±°ë¨" +#: src/view/screens/ProfileFeed.tsx:208 +msgid "Removed from your feeds" +msgstr "ë‚´ 피드ì—서 ì œê±°ë¨" + #: src/view/com/composer/ExternalEmbed.tsx:71 msgid "Removes default thumbnail from {0}" msgstr "{0}ì—서 기본 미리보기 ì´ë¯¸ì§€ë¥¼ ì œê±°í•©ë‹ˆë‹¤" -#: src/view/screens/Profile.tsx:181 +#: src/view/screens/Profile.tsx:189 msgid "Replies" msgstr "답글" @@ -3186,7 +3271,7 @@ msgstr "답글" msgid "Replies to this thread are disabled" msgstr "ì´ ìŠ¤ë ˆë“œì— ëŒ€í•œ ë‹µê¸€ì´ ë¹„í™œì„±í™”ë©ë‹ˆë‹¤." -#: src/view/com/composer/Composer.tsx:355 +#: src/view/com/composer/Composer.tsx:364 msgctxt "action" msgid "Reply" msgstr "답글" @@ -3195,34 +3280,51 @@ msgstr "답글" msgid "Reply Filters" msgstr "답글 í•„í„°" -#: src/view/com/post/Post.tsx:167 -#: src/view/com/posts/FeedItem.tsx:287 +#: src/view/com/post/Post.tsx:169 +#: src/view/com/posts/FeedItem.tsx:283 msgctxt "description" msgid "Reply to <0/>" msgstr "<0/> 님ì—게 보내는 답글" -#: src/view/com/modals/report/Modal.tsx:166 -msgid "Report {collectionName}" -msgstr "{collectionName} ì‹ ê³ " - -#: src/view/com/profile/ProfileHeader.tsx:361 +#: src/view/com/profile/ProfileMenu.tsx:319 +#: src/view/com/profile/ProfileMenu.tsx:322 msgid "Report Account" msgstr "ê³„ì • ì‹ ê³ " -#: src/view/screens/ProfileFeed.tsx:293 +#: src/view/screens/ProfileFeed.tsx:351 +#: src/view/screens/ProfileFeed.tsx:353 msgid "Report feed" msgstr "피드 ì‹ ê³ " -#: src/view/screens/ProfileList.tsx:459 +#: src/view/screens/ProfileList.tsx:429 msgid "Report List" msgstr "리스트 ì‹ ê³ " -#: src/view/com/modals/report/SendReportButton.tsx:37 -#: src/view/com/util/forms/PostDropdownBtn.tsx:301 -#: src/view/com/util/forms/PostDropdownBtn.tsx:309 +#: src/view/com/util/forms/PostDropdownBtn.tsx:292 +#: src/view/com/util/forms/PostDropdownBtn.tsx:294 msgid "Report post" msgstr "게시물 ì‹ ê³ " +#: src/components/ReportDialog/SelectReportOptionView.tsx:42 +msgid "Report this content" +msgstr "ì´ ì½˜í…ì¸ ì‹ ê³ í•˜ê¸°" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:55 +msgid "Report this feed" +msgstr "ì´ í”¼ë“œ ì‹ ê³ í•˜ê¸°" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:52 +msgid "Report this list" +msgstr "ì´ ë¦¬ìŠ¤íŠ¸ ì‹ ê³ í•˜ê¸°" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:49 +msgid "Report this post" +msgstr "ì´ ê²Œì‹œë¬¼ ì‹ ê³ í•˜ê¸°" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:46 +msgid "Report this user" +msgstr "ì´ ì‚¬ìš©ìž ì‹ ê³ í•˜ê¸°" + #: src/view/com/modals/Repost.tsx:43 #: src/view/com/modals/Repost.tsx:48 #: src/view/com/modals/Repost.tsx:53 @@ -3244,11 +3346,11 @@ msgstr "재게시 ë˜ëŠ” 게시물 ì¸ìš©" msgid "Reposted By" msgstr "재게시한 사용ìž" -#: src/view/com/posts/FeedItem.tsx:207 +#: src/view/com/posts/FeedItem.tsx:201 msgid "Reposted by {0}" msgstr "{0} ë‹˜ì´ ìž¬ê²Œì‹œí•¨" -#: src/view/com/posts/FeedItem.tsx:224 +#: src/view/com/posts/FeedItem.tsx:218 msgid "Reposted by <0/>" msgstr "<0/> ë‹˜ì´ ìž¬ê²Œì‹œí•¨" @@ -3256,7 +3358,7 @@ msgstr "<0/> ë‹˜ì´ ìž¬ê²Œì‹œí•¨" msgid "reposted your post" msgstr "ë‹˜ì´ ë‚´ ê²Œì‹œë¬¼ì„ ìž¬ê²Œì‹œí–ˆìŠµë‹ˆë‹¤" -#: src/view/com/post-thread/PostThreadItem.tsx:188 +#: src/view/com/post-thread/PostThreadItem.tsx:187 msgid "Reposts of this post" msgstr "ì´ ê²Œì‹œë¬¼ì˜ ìž¬ê²Œì‹œ" @@ -3265,16 +3367,12 @@ msgstr "ì´ ê²Œì‹œë¬¼ì˜ ìž¬ê²Œì‹œ" msgid "Request Change" msgstr "변경 ìš”ì²" -#: src/view/com/auth/create/Step2.tsx:219 -#~ msgid "Request code" -#~ msgstr "코드 ìš”ì²" - #: src/view/com/modals/ChangePassword.tsx:239 #: src/view/com/modals/ChangePassword.tsx:241 msgid "Request Code" -msgstr "" +msgstr "코드 ìš”ì²" -#: src/view/screens/Settings/index.tsx:456 +#: src/view/screens/Settings/index.tsx:474 msgid "Require alt text before posting" msgstr "게시하기 ì „ 대체 í…스트 필수" @@ -3289,13 +3387,13 @@ msgstr "ìž¬ì„¤ì • 코드" #: src/view/com/modals/ChangePassword.tsx:190 msgid "Reset Code" -msgstr "" +msgstr "ìž¬ì„¤ì • 코드" -#: src/view/screens/Settings/index.tsx:824 +#: src/view/screens/Settings/index.tsx:852 msgid "Reset onboarding" msgstr "온보딩 초기화" -#: src/view/screens/Settings/index.tsx:827 +#: src/view/screens/Settings/index.tsx:855 msgid "Reset onboarding state" msgstr "온보딩 ìƒíƒœ 초기화" @@ -3303,19 +3401,19 @@ msgstr "온보딩 ìƒíƒœ 초기화" msgid "Reset password" msgstr "비밀번호 ìž¬ì„¤ì •" -#: src/view/screens/Settings/index.tsx:814 +#: src/view/screens/Settings/index.tsx:842 msgid "Reset preferences" msgstr "ì„¤ì • 초기화" -#: src/view/screens/Settings/index.tsx:817 +#: src/view/screens/Settings/index.tsx:845 msgid "Reset preferences state" msgstr "ì„¤ì • ìƒíƒœ 초기화" -#: src/view/screens/Settings/index.tsx:825 +#: src/view/screens/Settings/index.tsx:853 msgid "Resets the onboarding state" msgstr "온보딩 ìƒíƒœ 초기화" -#: src/view/screens/Settings/index.tsx:815 +#: src/view/screens/Settings/index.tsx:843 msgid "Resets the preferences state" msgstr "ì„¤ì • ìƒíƒœ 초기화" @@ -3339,17 +3437,16 @@ msgstr "오류가 ë°œìƒí•œ 마지막 ìž‘ì—…ì„ ë‹¤ì‹œ 시ë„합니다" msgid "Retry" msgstr "다시 시ë„" -#: src/view/com/auth/create/Step2.tsx:247 -#~ msgid "Retry." -#~ msgstr "다시 시ë„하기" - -#: src/view/screens/ProfileList.tsx:903 +#: src/view/screens/ProfileList.tsx:917 msgid "Return to previous page" msgstr "ì´ì „ 페ì´ì§€ë¡œ ëŒì•„갑니다" -#: src/view/shell/desktop/RightNav.tsx:55 -#~ msgid "SANDBOX. Posts and accounts are not permanent." -#~ msgstr "샌드박스. 글과 ê³„ì •ì€ ì˜êµ¬ì ì´ì§€ 않습니다." +#: src/components/dialogs/BirthDateSettings.tsx:118 +#: src/view/com/modals/ChangeHandle.tsx:173 +#: src/view/com/modals/CreateOrEditList.tsx:337 +#: src/view/com/modals/EditProfile.tsx:224 +msgid "Save" +msgstr "ì €ìž¥" #: src/view/com/lightbox/Lightbox.tsx:132 #: src/view/com/modals/CreateOrEditList.tsx:345 @@ -3357,19 +3454,14 @@ msgctxt "action" msgid "Save" msgstr "ì €ìž¥" -#: src/view/com/modals/BirthDateSettings.tsx:94 -#: src/view/com/modals/BirthDateSettings.tsx:97 -#: src/view/com/modals/ChangeHandle.tsx:173 -#: src/view/com/modals/CreateOrEditList.tsx:337 -#: src/view/com/modals/EditProfile.tsx:224 -#: src/view/screens/ProfileFeed.tsx:346 -msgid "Save" -msgstr "ì €ìž¥" - #: src/view/com/modals/AltImage.tsx:130 msgid "Save alt text" msgstr "대체 í…스트 ì €ìž¥" +#: src/components/dialogs/BirthDateSettings.tsx:112 +msgid "Save birthday" +msgstr "ìƒë…„ì›”ì¼ ì €ìž¥" + #: src/view/com/modals/EditProfile.tsx:232 msgid "Save Changes" msgstr "변경 ì‚¬í• ì €ìž¥" @@ -3382,10 +3474,19 @@ msgstr "핸들 변경 ì €ìž¥" msgid "Save image crop" msgstr "ì´ë¯¸ì§€ ìžë¥´ê¸° ì €ìž¥" +#: src/view/screens/ProfileFeed.tsx:335 +#: src/view/screens/ProfileFeed.tsx:341 +msgid "Save to my feeds" +msgstr "ë‚´ í”¼ë“œì— ì €ìž¥" + #: src/view/screens/SavedFeeds.tsx:122 msgid "Saved Feeds" msgstr "ì €ìž¥ëœ í”¼ë“œ" +#: src/view/screens/ProfileFeed.tsx:212 +msgid "Saved to your feeds" +msgstr "ë‚´ í”¼ë“œì— ì €ìž¥ë¨" + #: src/view/com/modals/EditProfile.tsx:225 msgid "Saves any changes to your profile" msgstr "í”„ë¡œí•„ì— ëŒ€í•œ ëª¨ë“ ë³€ê²½ 사í•ì„ ì €ìž¥í•©ë‹ˆë‹¤" @@ -3398,11 +3499,11 @@ msgstr "í•¸ë“¤ì„ {handle}(으)로 변경합니다" msgid "Science" msgstr "과학" -#: src/view/screens/ProfileList.tsx:859 +#: src/view/screens/ProfileList.tsx:873 msgid "Scroll to top" msgstr "맨 위로 스í¬ë¡¤" -#: src/Navigation.tsx:447 +#: src/Navigation.tsx:459 #: src/view/com/auth/LoggedOut.tsx:122 #: src/view/com/modals/ListAddRemoveUsers.tsx:75 #: src/view/com/util/forms/SearchInput.tsx:67 @@ -3410,7 +3511,7 @@ msgstr "맨 위로 스í¬ë¡¤" #: src/view/screens/Search/Search.tsx:419 #: src/view/screens/Search/Search.tsx:668 #: src/view/screens/Search/Search.tsx:686 -#: src/view/shell/bottom-bar/BottomBar.tsx:159 +#: src/view/shell/bottom-bar/BottomBar.tsx:161 #: src/view/shell/desktop/LeftNav.tsx:324 #: src/view/shell/desktop/Search.tsx:214 #: src/view/shell/desktop/Search.tsx:223 @@ -3426,19 +3527,11 @@ msgstr "\"{query}\"ì— ëŒ€í•œ 검색 ê²°ê³¼" #: src/components/TagMenu/index.tsx:145 msgid "Search for all posts by @{authorHandle} with tag {displayTag}" -msgstr "" - -#: src/components/TagMenu/index.tsx:145 -#~ msgid "Search for all posts by @{authorHandle} with tag {tag}" -#~ msgstr "" +msgstr "{displayTag} 태그를 사용한 @{authorHandle} ë‹˜ì˜ ëª¨ë“ ê²Œì‹œë¬¼ 검색" #: src/components/TagMenu/index.tsx:94 msgid "Search for all posts with tag {displayTag}" -msgstr "" - -#: src/components/TagMenu/index.tsx:90 -#~ msgid "Search for all posts with tag {tag}" -#~ msgstr "" +msgstr "{displayTag} 태그를 사용한 ëª¨ë“ ê²Œì‹œë¬¼ 검색" #: src/view/com/auth/LoggedOut.tsx:104 #: src/view/com/auth/LoggedOut.tsx:105 @@ -3452,27 +3545,19 @@ msgstr "보안 단계 í•„ìš”" #: src/components/TagMenu/index.web.tsx:66 msgid "See {truncatedTag} posts" -msgstr "" +msgstr "{truncatedTag} 게시물 보기" #: src/components/TagMenu/index.web.tsx:83 msgid "See {truncatedTag} posts by user" -msgstr "" +msgstr "ì´ ì‚¬ìš©ìžì˜ {truncatedTag} 게시물 보기" #: src/components/TagMenu/index.tsx:128 msgid "See <0>{displayTag}</0> posts" -msgstr "" +msgstr "<0>{displayTag}</0> 게시물 보기" #: src/components/TagMenu/index.tsx:187 msgid "See <0>{displayTag}</0> posts by this user" -msgstr "" - -#: src/components/TagMenu/index.tsx:128 -#~ msgid "See <0>{tag}</0> posts" -#~ msgstr "" - -#: src/components/TagMenu/index.tsx:189 -#~ msgid "See <0>{tag}</0> posts by this user" -#~ msgstr "" +msgstr "ì´ ì‚¬ìš©ìžì˜ <0>{displayTag}</0> 게시물 보기" #: src/view/screens/SavedFeeds.tsx:163 msgid "See this guide" @@ -3486,14 +3571,14 @@ msgstr "See what's next" msgid "Select {item}" msgstr "{item} ì„ íƒ" -#: src/view/com/modals/ServerInput.tsx:75 -#~ msgid "Select Bluesky Social" -#~ msgstr "Bluesky Social ì„ íƒ" - #: src/view/com/auth/login/Login.tsx:117 msgid "Select from an existing account" msgstr "기존 ê³„ì •ì—서 ì„ íƒ" +#: src/components/ReportDialog/SelectLabelerView.tsx:30 +msgid "Select moderation service" +msgstr "ê²€í† ì„œë¹„ìŠ¤ ì„ íƒí•˜ê¸°" + #: src/view/com/util/Selector.tsx:107 msgid "Select option {i} of {numItems}" msgstr "{numItems}ê°œ 중 {i}번째 ì˜µì…˜ì„ ì„ íƒí•©ë‹ˆë‹¤" @@ -3507,15 +3592,19 @@ msgstr "서비스 ì„ íƒ" msgid "Select some accounts below to follow" msgstr "아래ì—서 íŒ”ë¡œìš°í• ê³„ì •ì„ ì„ íƒí•˜ì„¸ìš”" +#: 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 "" +msgstr "ë°ì´í„°ë¥¼ í˜¸ìŠ¤íŒ…í• ì„œë¹„ìŠ¤ë¥¼ ì„ íƒí•˜ì„¸ìš”." #: src/screens/Onboarding/StepTopicalFeeds.tsx:96 msgid "Select topical feeds to follow from the list below" msgstr "아래 목ë¡ì—서 íŒ”ë¡œìš°í• í™”ì œ 피드를 ì„ íƒí•˜ì„¸ìš”" -#: src/screens/Onboarding/StepModeration/index.tsx:75 +#: src/screens/Onboarding/StepModeration/index.tsx:62 msgid "Select what you want to see (or not see), and we’ll handle the rest." msgstr "ë³´ê³ ì‹¶ê±°ë‚˜ ë³´ê³ ì‹¶ì§€ ì•Šì€ í•ëª©ì„ ì„ íƒí•˜ë©´ 나머지는 알아서 처리해 드립니다." @@ -3531,10 +3620,6 @@ msgstr "ì•±ì— í‘œì‹œë˜ëŠ” 기본 í…스트 언어를 ì„ íƒí•©ë‹ˆë‹¤." msgid "Select your interests from the options below" msgstr "아래 옵션ì—서 관심사를 ì„ íƒí•˜ì„¸ìš”" -#: src/view/com/auth/create/Step2.tsx:155 -#~ msgid "Select your phone's country" -#~ msgstr "ì „í™”ë²ˆí˜¸ êµê°€ ì„ íƒ" - #: src/view/screens/LanguageSettings.tsx:190 msgid "Select your preferred language for translations in your feed." msgstr "피드ì—서 번ì—ì„ ìœ„í•´ ì„ í˜¸í•˜ëŠ” 언어를 ì„ íƒí•©ë‹ˆë‹¤." @@ -3566,47 +3651,46 @@ msgstr "ì´ë©”ì¼ ë³´ë‚´ê¸°" msgid "Send feedback" msgstr "피드백 보내기" -#: src/view/com/modals/report/SendReportButton.tsx:45 -msgid "Send Report" +#: src/components/ReportDialog/SubmitView.tsx:214 +#: src/components/ReportDialog/SubmitView.tsx:218 +msgid "Send report" msgstr "ì‹ ê³ ë³´ë‚´ê¸°" +#: src/components/ReportDialog/SelectLabelerView.tsx:44 +msgid "Send report to {0}" +msgstr "{0} 님ì—게 ì‹ ê³ ë³´ë‚´ê¸°" + #: src/view/com/modals/DeleteAccount.tsx:133 msgid "Sends email with confirmation code for account deletion" msgstr "ê³„ì • ì‚ì œë¥¼ 위한 í™•ì¸ ì½”ë“œê°€ í¬í•¨ëœ ì´ë©”ì¼ì„ ì „ì†¡í•©ë‹ˆë‹¤" #: src/view/com/auth/server-input/index.tsx:110 msgid "Server address" -msgstr "" +msgstr "서버 주소" -#: src/view/com/modals/ContentFilteringSettings.tsx:311 -msgid "Set {value} for {labelGroup} content moderation policy" -msgstr "{labelGroup} 콘í…ì¸ ê´€ë¦¬ ì •ì±…ì— ëŒ€í•´ {value}ì„(를) ì„¤ì •í•©ë‹ˆë‹¤" +#: src/screens/Moderation/index.tsx:307 +msgid "Set birthdate" +msgstr "ìƒë…„ì›”ì¼ ì„¤ì •" -#: src/view/com/modals/ContentFilteringSettings.tsx:160 -#: src/view/com/modals/ContentFilteringSettings.tsx:179 -msgctxt "action" -msgid "Set Age" -msgstr "ë‚˜ì´ ì„¤ì •" - -#: src/view/screens/Settings/index.tsx:488 +#: src/view/screens/Settings/index.tsx:506 msgid "Set color theme to dark" msgstr "ìƒ‰ìƒ í…Œë§ˆë¥¼ ì–´ë‘움으로 ì„¤ì •í•©ë‹ˆë‹¤" -#: src/view/screens/Settings/index.tsx:481 +#: src/view/screens/Settings/index.tsx:499 msgid "Set color theme to light" msgstr "ìƒ‰ìƒ í…Œë§ˆë¥¼ ë°ìŒìœ¼ë¡œ ì„¤ì •í•©ë‹ˆë‹¤" -#: src/view/screens/Settings/index.tsx:475 +#: src/view/screens/Settings/index.tsx:493 msgid "Set color theme to system setting" msgstr "ìƒ‰ìƒ í…Œë§ˆë¥¼ 시스템 ì„¤ì •ì— ë§žì¶¥ë‹ˆë‹¤" -#: src/view/screens/Settings/index.tsx:514 +#: src/view/screens/Settings/index.tsx:532 msgid "Set dark theme to the dark theme" -msgstr "" +msgstr "ì–´ë‘ìš´ 테마를 ì™„ì „ížˆ 어둡게 ì„¤ì •í•©ë‹ˆë‹¤" -#: src/view/screens/Settings/index.tsx:507 +#: src/view/screens/Settings/index.tsx:525 msgid "Set dark theme to the dim theme" -msgstr "" +msgstr "ì–´ë‘ìš´ 테마를 ì‚´ì§ ë°ê²Œ ì„¤ì •í•©ë‹ˆë‹¤" #: src/view/com/auth/login/SetNewPasswordForm.tsx:104 msgid "Set new password" @@ -3632,13 +3716,9 @@ msgstr "피드ì—서 ëª¨ë“ ìž¬ê²Œì‹œë¥¼ ìˆ¨ê¸°ë ¤ë©´ ì´ ì„¤ì •ì„ \"아니요\ msgid "Set this setting to \"Yes\" to show replies in a threaded view. This is an experimental feature." msgstr "ìŠ¤ë ˆë“œ ë³´ê¸°ì— ë‹µê¸€ì„ í‘œì‹œí•˜ë ¤ë©´ ì´ ì„¤ì •ì„ \"예\"로 ì„¤ì •í•©ë‹ˆë‹¤. ì´ëŠ” 실험ì ì¸ ê¸°ëŠ¥ìž…ë‹ˆë‹¤." -#: src/view/screens/PreferencesHomeFeed.tsx:261 -#~ msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your following feed. This is an experimental feature." -#~ msgstr "팔로우한 í”¼ë“œì— ì €ìž¥ëœ í”¼ë“œ ìƒ˜í”Œì„ í‘œì‹œí•˜ë ¤ë©´ ì´ ì„¤ì •ì„ \"예\"로 ì„¤ì •í•©ë‹ˆë‹¤. ì´ëŠ” 실험ì ì¸ ê¸°ëŠ¥ìž…ë‹ˆë‹¤." - #: src/view/screens/PreferencesFollowingFeed.tsx:261 msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your Following feed. This is an experimental feature." -msgstr "" +msgstr "팔로우 중 í”¼ë“œì— ì €ìž¥ëœ í”¼ë“œ ìƒ˜í”Œì„ í‘œì‹œí•˜ë ¤ë©´ ì´ ì„¤ì •ì„ \"예\"로 ì„¤ì •í•©ë‹ˆë‹¤. ì´ëŠ” 실험ì ì¸ ê¸°ëŠ¥ìž…ë‹ˆë‹¤." #: src/screens/Onboarding/Layout.tsx:50 msgid "Set up your account" @@ -3661,8 +3741,8 @@ msgstr "비밀번호 ìž¬ì„¤ì •ì„ ìœ„í•œ 호스팅 ì œê³µìžë¥¼ ì„¤ì •í•©ë‹ˆë‹¤" msgid "Sets server for the Bluesky client" msgstr "Bluesky í´ë¼ì´ì–¸íŠ¸ë¥¼ 위한 서버를 ì„¤ì •í•©ë‹ˆë‹¤" -#: src/Navigation.tsx:137 -#: src/view/screens/Settings/index.tsx:294 +#: src/Navigation.tsx:139 +#: src/view/screens/Settings/index.tsx:312 #: src/view/shell/desktop/LeftNav.tsx:433 #: src/view/shell/Drawer.tsx:567 #: src/view/shell/Drawer.tsx:568 @@ -3673,28 +3753,39 @@ msgstr "ì„¤ì •" msgid "Sexual activity or erotic nudity." msgstr "성행위 ë˜ëŠ” ì„ ì •ì ì¸ ë…¸ì¶œ." +#: src/lib/moderation/useGlobalLabelStrings.ts:38 +msgid "Sexually Suggestive" +msgstr "외설ì " + #: src/view/com/lightbox/Lightbox.tsx:141 msgctxt "action" msgid "Share" msgstr "ê³µìœ " -#: src/view/com/profile/ProfileHeader.tsx:295 -#: src/view/com/util/forms/PostDropdownBtn.tsx:231 +#: src/view/com/profile/ProfileMenu.tsx:215 +#: src/view/com/profile/ProfileMenu.tsx:224 +#: src/view/com/util/forms/PostDropdownBtn.tsx:228 #: src/view/com/util/forms/PostDropdownBtn.tsx:237 -#: src/view/com/util/post-ctrls/PostCtrls.tsx:215 -#: src/view/screens/ProfileList.tsx:418 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:218 +#: src/view/screens/ProfileList.tsx:388 msgid "Share" msgstr "ê³µìœ " -#: src/view/screens/ProfileFeed.tsx:305 +#: src/view/com/profile/ProfileMenu.tsx:373 +#: src/view/com/util/forms/PostDropdownBtn.tsx:347 +msgid "Share anyway" +msgstr "ë¬´ì‹œí•˜ê³ ê³µìœ " + +#: src/view/screens/ProfileFeed.tsx:361 +#: src/view/screens/ProfileFeed.tsx:363 msgid "Share feed" msgstr "피드 ê³µìœ " -#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:43 -#: src/view/com/modals/ContentFilteringSettings.tsx:266 -#: src/view/com/util/moderation/ContentHider.tsx:107 -#: src/view/com/util/moderation/PostHider.tsx:108 -#: src/view/screens/Settings/index.tsx:344 +#: src/components/moderation/ContentHider.tsx:115 +#: src/components/moderation/GlobalModerationLabelPref.tsx:45 +#: src/components/moderation/PostHider.tsx:107 +#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:54 +#: src/view/screens/Settings/index.tsx:362 msgid "Show" msgstr "표시" @@ -3702,21 +3793,31 @@ msgstr "표시" msgid "Show all replies" msgstr "ëª¨ë“ ë‹µê¸€ 표시" -#: src/view/com/util/moderation/ScreenHider.tsx:132 +#: src/components/moderation/ScreenHider.tsx:161 +#: src/components/moderation/ScreenHider.tsx:164 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/view/com/modals/EmbedConsent.tsx:87 msgid "Show embeds from {0}" msgstr "{0} ìž„ë² ë“œ 표시" -#: src/view/com/profile/ProfileHeader.tsx:459 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:193 msgid "Show follows similar to {0}" msgstr "{0} 님과 비슷한 팔로우 표시" -#: src/view/com/post-thread/PostThreadItem.tsx:538 -#: src/view/com/post/Post.tsx:198 -#: src/view/com/posts/FeedItem.tsx:363 +#: src/view/com/post-thread/PostThreadItem.tsx:509 +#: src/view/com/post/Post.tsx:204 +#: src/view/com/posts/FeedItem.tsx:358 msgid "Show More" msgstr "ë” ë³´ê¸°" @@ -3730,15 +3831,15 @@ msgstr "ì¸ìš© 게시물 표시" #: src/screens/Onboarding/StepFollowingFeed.tsx:118 msgid "Show quote-posts in Following feed" -msgstr "팔로우 ì¤‘ì¸ í”¼ë“œì— ì¸ìš© 게시물 표시" +msgstr "팔로우 중 í”¼ë“œì— ì¸ìš© 게시물 표시" #: src/screens/Onboarding/StepFollowingFeed.tsx:134 msgid "Show quotes in Following" -msgstr "팔로우 ì¤‘ì¸ í”¼ë“œì— ì¸ìš© 표시" +msgstr "팔로우 중 í”¼ë“œì— ì¸ìš© 표시" #: src/screens/Onboarding/StepFollowingFeed.tsx:94 msgid "Show re-posts in Following feed" -msgstr "팔로우 ì¤‘ì¸ í”¼ë“œì— ìž¬ê²Œì‹œ 표시" +msgstr "팔로우 중 í”¼ë“œì— ìž¬ê²Œì‹œ 표시" #: src/view/screens/PreferencesFollowingFeed.tsx:119 msgid "Show Replies" @@ -3750,11 +3851,11 @@ msgstr "ë‚´ê°€ 팔로우하는 ì‚¬ëžŒë“¤ì˜ ë‹µê¸€ì„ ë‹¤ë¥¸ ëª¨ë“ ë‹µê¸€ë³´ë‹¤ #: src/screens/Onboarding/StepFollowingFeed.tsx:86 msgid "Show replies in Following" -msgstr "팔로우 ì¤‘ì¸ í”¼ë“œì— ë‹µê¸€ 표시" +msgstr "팔로우 중 í”¼ë“œì— ë‹µê¸€ 표시" #: src/screens/Onboarding/StepFollowingFeed.tsx:70 msgid "Show replies in Following feed" -msgstr "팔로우 ì¤‘ì¸ í”¼ë“œì— ë‹µê¸€ 표시" +msgstr "팔로우 중 í”¼ë“œì— ë‹µê¸€ 표시" #: src/view/screens/PreferencesFollowingFeed.tsx:70 msgid "Show replies with at least {value} {0}" @@ -3766,32 +3867,35 @@ msgstr "재게시 표시" #: src/screens/Onboarding/StepFollowingFeed.tsx:110 msgid "Show reposts in Following" -msgstr "팔로우 ì¤‘ì¸ í”¼ë“œì— ìž¬ê²Œì‹œ 표시" +msgstr "팔로우 중 í”¼ë“œì— ìž¬ê²Œì‹œ 표시" -#: src/view/com/util/moderation/ContentHider.tsx:67 -#: src/view/com/util/moderation/PostHider.tsx:61 +#: src/components/moderation/ContentHider.tsx:68 +#: src/components/moderation/PostHider.tsx:64 msgid "Show the content" msgstr "콘í…ì¸ í‘œì‹œ" -#: src/view/com/notifications/FeedItem.tsx:347 +#: src/view/com/notifications/FeedItem.tsx:346 msgid "Show users" msgstr "ì‚¬ìš©ìž í‘œì‹œ" -#: src/view/com/profile/ProfileHeader.tsx:462 -msgid "Shows a list of users similar to this user." -msgstr "ì´ ì‚¬ìš©ìžì™€ ìœ ì‚¬í•œ ì‚¬ìš©ìž ëª©ë¡ì„ 표시합니다" +#: src/lib/moderation/useLabelBehaviorDescription.ts:58 +msgid "Show warning" +msgstr "ê²½ê³ í‘œì‹œ" -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:124 -#: src/view/com/profile/ProfileHeader.tsx:506 +#: src/lib/moderation/useLabelBehaviorDescription.ts:56 +msgid "Show warning and filter from feeds" +msgstr "ê²½ê³ í‘œì‹œ ë° í”¼ë“œì—서 í•„í„°ë§" + +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:127 msgid "Shows posts from {0} in your feed" msgstr "í”¼ë“œì— {0} ë‹˜ì˜ ê²Œì‹œë¬¼ì„ í‘œì‹œí•©ë‹ˆë‹¤" #: src/view/com/auth/HomeLoggedOutCTA.tsx:70 #: src/view/com/auth/login/Login.tsx:98 #: src/view/com/auth/SplashScreen.tsx:79 -#: src/view/shell/bottom-bar/BottomBar.tsx:285 -#: src/view/shell/bottom-bar/BottomBar.tsx:286 +#: src/view/shell/bottom-bar/BottomBar.tsx:287 #: src/view/shell/bottom-bar/BottomBar.tsx:288 +#: src/view/shell/bottom-bar/BottomBar.tsx:290 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:178 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:179 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:181 @@ -3821,14 +3925,14 @@ msgstr "로그ì¸" #: src/view/com/modals/SwitchAccount.tsx:64 #: src/view/com/modals/SwitchAccount.tsx:69 -#: src/view/screens/Settings/index.tsx:100 -#: src/view/screens/Settings/index.tsx:103 +#: src/view/screens/Settings/index.tsx:104 +#: src/view/screens/Settings/index.tsx:107 msgid "Sign out" msgstr "로그아웃" -#: src/view/shell/bottom-bar/BottomBar.tsx:275 -#: src/view/shell/bottom-bar/BottomBar.tsx:276 +#: src/view/shell/bottom-bar/BottomBar.tsx:277 #: src/view/shell/bottom-bar/BottomBar.tsx:278 +#: src/view/shell/bottom-bar/BottomBar.tsx:280 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:168 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:169 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:171 @@ -3842,11 +3946,12 @@ msgstr "가입하기" msgid "Sign up or sign in to join the conversation" msgstr "가입 ë˜ëŠ” 로그ì¸í•˜ì—¬ ëŒ€í™”ì— ì°¸ì—¬í•˜ì„¸ìš”" -#: src/view/com/util/moderation/ScreenHider.tsx:76 +#: src/components/moderation/ScreenHider.tsx:97 +#: src/lib/moderation/useGlobalLabelStrings.ts:28 msgid "Sign-in Required" msgstr "ë¡œê·¸ì¸ í•„ìš”" -#: src/view/screens/Settings/index.tsx:355 +#: src/view/screens/Settings/index.tsx:373 msgid "Signed in as" msgstr "로그ì¸í•œ ê³„ì •" @@ -3868,27 +3973,21 @@ msgstr "건너뛰기" msgid "Skip this flow" msgstr "ì´ ë‹¨ê³„ 건너뛰기" -#: src/view/com/auth/create/Step2.tsx:82 -#~ msgid "SMS verification" -#~ msgstr "SMS ì¸ì¦" - #: src/screens/Onboarding/index.tsx:40 msgid "Software Dev" msgstr "소프트웨어 개발" -#: src/view/com/modals/ProfilePreview.tsx:62 -#~ msgid "Something went wrong and we're not sure what." -#~ msgstr "ë¬¸ì œê°€ ë°œìƒí–ˆì§€ë§Œ ì›ì¸ì„ 알 수 없습니다." +#: src/components/ReportDialog/index.tsx:50 +#: src/screens/Moderation/index.tsx:116 +#: src/screens/Profile/Sections/Labels.tsx:88 +msgid "Something went wrong, please try again." +msgstr "ë”ê°€ 잘못ë˜ì—ˆìŠµë‹ˆë‹¤. 다시 시ë„í•´ 주세요." -#: src/components/Lists.tsx:203 +#: src/components/Lists.tsx:202 msgid "Something went wrong!" -msgstr "" +msgstr "ë”ê°€ 잘못ë˜ì—ˆìŠµë‹ˆë‹¤!" -#: src/view/com/modals/Waitlist.tsx:51 -#~ msgid "Something went wrong. Check your email and try again." -#~ msgstr "ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì´ë©”ì¼ì„ 확ì¸í•œ 후 다시 시ë„하세요." - -#: src/App.native.tsx:66 +#: src/App.native.tsx:71 msgid "Sorry! Your session expired. Please log in again." msgstr "죄송합니다. ì„¸ì…˜ì´ ë§Œë£Œë˜ì—ˆìŠµë‹ˆë‹¤. 다시 로그ì¸í•´ 주세요." @@ -3900,6 +3999,18 @@ msgstr "답글 ì •ë ¬" msgid "Sort replies to the same post by:" msgstr "ë™ì¼í•œ ê²Œì‹œë¬¼ì— ëŒ€í•œ ë‹µê¸€ì„ ì •ë ¬í•˜ëŠ” 기준입니다." +#: src/components/moderation/LabelsOnMeDialog.tsx:147 +msgid "Source:" +msgstr "출처:" + +#: src/lib/moderation/useReportOptions.ts:65 +msgid "Spam" +msgstr "스팸" + +#: src/lib/moderation/useReportOptions.ts:53 +msgid "Spam; excessive mentions or replies" +msgstr "스팸, ê³¼ë„한 멘션 ë˜ëŠ” 답글" + #: src/screens/Onboarding/index.tsx:30 msgid "Sports" msgstr "스í¬ì¸ " @@ -3908,11 +4019,7 @@ msgstr "스í¬ì¸ " msgid "Square" msgstr "ì •ì‚¬ê°í˜•" -#: src/view/com/modals/ServerInput.tsx:62 -#~ msgid "Staging" -#~ msgstr "스테ì´ì§•" - -#: src/view/screens/Settings/index.tsx:871 +#: src/view/screens/Settings/index.tsx:899 msgid "Status page" msgstr "ìƒíƒœ 페ì´ì§€" @@ -3920,29 +4027,42 @@ msgstr "ìƒíƒœ 페ì´ì§€" msgid "Step {0} of {numSteps}" msgstr "{numSteps}단계 중 {0}단계" -#: src/view/screens/Settings/index.tsx:274 +#: src/view/screens/Settings/index.tsx:288 msgid "Storage cleared, you need to restart the app now." msgstr "ìŠ¤í† ë¦¬ì§€ê°€ 지워졌으며 지금 ì•±ì„ ë‹¤ì‹œ 시작해야 합니다." -#: src/Navigation.tsx:204 -#: src/view/screens/Settings/index.tsx:807 +#: src/Navigation.tsx:211 +#: src/view/screens/Settings/index.tsx:825 msgid "Storybook" msgstr "ìŠ¤í† ë¦¬ë¶" -#: src/view/com/modals/AppealLabel.tsx:101 +#: src/components/moderation/LabelsOnMeDialog.tsx:256 +#: src/components/moderation/LabelsOnMeDialog.tsx:257 msgid "Submit" msgstr "확ì¸" -#: src/view/screens/ProfileList.tsx:608 +#: src/view/screens/ProfileList.tsx:590 msgid "Subscribe" msgstr "구ë…" +#: src/screens/Profile/Sections/Labels.tsx:199 +msgid "Subscribe to @{0} to use these labels:" +msgstr "ì´ ë¼ë²¨ì„ ì‚¬ìš©í•˜ë ¤ë©´ @{0} ë‹˜ì„ êµ¬ë…하세요:" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:222 +msgid "Subscribe to Labeler" +msgstr "ë¼ë²¨ëŸ¬ 구ë…" + #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:173 #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:308 msgid "Subscribe to the {0} feed" msgstr "{0} 피드 구ë…하기" -#: src/view/screens/ProfileList.tsx:604 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:185 +msgid "Subscribe to this labeler" +msgstr "ì´ ë¼ë²¨ëŸ¬ 구ë…하기" + +#: src/view/screens/ProfileList.tsx:586 msgid "Subscribe to this list" msgstr "ì´ ë¦¬ìŠ¤íŠ¸ 구ë…하기" @@ -3958,49 +4078,41 @@ msgstr "나를 위한 추천" msgid "Suggestive" msgstr "외설ì " -#: src/Navigation.tsx:214 +#: src/Navigation.tsx:226 #: src/view/screens/Support.tsx:30 #: src/view/screens/Support.tsx:33 msgid "Support" msgstr "ì§€ì›" -#: src/view/com/modals/ProfilePreview.tsx:110 -#~ msgid "Swipe up to see more" -#~ msgstr "위로 스와ì´í”„하여 ë” ë³´ê¸°" - #: src/view/com/modals/SwitchAccount.tsx:117 msgid "Switch Account" msgstr "ê³„ì • ì „í™˜" #: src/view/com/modals/SwitchAccount.tsx:97 -#: src/view/screens/Settings/index.tsx:130 +#: src/view/screens/Settings/index.tsx:134 msgid "Switch to {0}" msgstr "{0}(으)로 ì „í™˜" #: src/view/com/modals/SwitchAccount.tsx:98 -#: src/view/screens/Settings/index.tsx:131 +#: src/view/screens/Settings/index.tsx:135 msgid "Switches the account you are logged in to" msgstr "로그ì¸í•œ ê³„ì •ì„ ì „í™˜í•©ë‹ˆë‹¤" -#: src/view/screens/Settings/index.tsx:472 +#: src/view/screens/Settings/index.tsx:490 msgid "System" msgstr "시스템" -#: src/view/screens/Settings/index.tsx:795 +#: src/view/screens/Settings/index.tsx:813 msgid "System log" msgstr "시스템 로그" -#: src/components/dialogs/MutedWords.tsx:337 +#: src/components/dialogs/MutedWords.tsx:324 msgid "tag" -msgstr "" +msgstr "태그" #: src/components/TagMenu/index.tsx:78 msgid "Tag menu: {displayTag}" -msgstr "" - -#: src/components/TagMenu/index.tsx:74 -#~ msgid "Tag menu: {tag}" -#~ msgstr "" +msgstr "태그 메뉴: {displayTag}" #: src/view/com/modals/crop-image/CropImage.web.tsx:112 msgid "Tall" @@ -4018,29 +4130,43 @@ msgstr "ê¸°ìˆ " msgid "Terms" msgstr "ì´ìš©ì•½ê´€" -#: src/Navigation.tsx:224 -#: src/view/screens/Settings/index.tsx:885 +#: src/Navigation.tsx:236 +#: src/view/screens/Settings/index.tsx:913 #: src/view/screens/TermsOfService.tsx:29 #: src/view/shell/Drawer.tsx:256 msgid "Terms of Service" msgstr "서비스 ì´ìš©ì•½ê´€" -#: src/components/dialogs/MutedWords.tsx:337 +#: src/lib/moderation/useReportOptions.ts:58 +#: src/lib/moderation/useReportOptions.ts:79 +#: src/lib/moderation/useReportOptions.ts:87 +msgid "Terms used violate community standards" +msgstr "커뮤니티 ê¸°ì¤€ì„ ìœ„ë°˜í•˜ëŠ” 용어 사용" + +#: src/components/dialogs/MutedWords.tsx:324 msgid "text" -msgstr "" +msgstr "글" -#: src/view/com/modals/AppealLabel.tsx:70 -#: src/view/com/modals/report/InputIssueDetails.tsx:51 +#: src/components/moderation/LabelsOnMeDialog.tsx:220 msgid "Text input field" msgstr "í…스트 ìž…ë ¥ 필드" +#: src/components/ReportDialog/SubmitView.tsx:78 +msgid "Thank you. Your report has been sent." +msgstr "ê°ì‚¬í•©ë‹ˆë‹¤. ì‹ ê³ ë¥¼ ì „ì†¡í–ˆìŠµë‹ˆë‹¤." + #: src/view/com/auth/create/CreateAccount.tsx:94 msgid "That handle is already taken." -msgstr "" +msgstr "ì´ í•¸ë“¤ì€ ì´ë¯¸ 사용 중입니다." -#: src/view/com/profile/ProfileHeader.tsx:263 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:274 +#: src/view/com/profile/ProfileMenu.tsx:349 msgid "The account will be able to interact with you after unblocking." -msgstr "ì°¨ë‹¨ì„ í•´ì œí•˜ë©´ 해당 ê³„ì •ì´ ë‚˜ì™€ ìƒí˜¸ìž‘ìš©í• ìˆ˜ 있게 ë©ë‹ˆë‹¤." +msgstr "ì°¨ë‹¨ì„ í•´ì œí•˜ë©´ ì´ ê³„ì •ì´ ë‚˜ì™€ ìƒí˜¸ìž‘ìš©í• ìˆ˜ 있게 ë©ë‹ˆë‹¤." + +#: src/components/moderation/ModerationDetailsDialog.tsx:128 +msgid "the author" +msgstr "작성ìž" #: src/view/screens/CommunityGuidelines.tsx:36 msgid "The Community Guidelines have been moved to <0/>" @@ -4050,11 +4176,19 @@ msgstr "커뮤니티 ê°€ì´ë“œë¼ì¸ì„ <0/>(으)로 ì´ë™í–ˆìŠµë‹ˆë‹¤" msgid "The Copyright Policy has been moved to <0/>" msgstr "ì €ìž‘ê¶Œ ì •ì±…ì„ <0/>(으)로 ì´ë™í–ˆìŠµë‹ˆë‹¤" +#: src/components/moderation/LabelsOnMeDialog.tsx:49 +msgid "The following labels were applied to your account." +msgstr "ë‚´ ê³„ì •ì— ë‹¤ìŒ ë¼ë²¨ì´ ì ìš©ë˜ì—ˆìŠµë‹ˆë‹¤." + +#: src/components/moderation/LabelsOnMeDialog.tsx:50 +msgid "The following labels were applied to your content." +msgstr "ë‚´ 콘í…ì¸ ì— ë‹¤ìŒ ë¼ë²¨ì´ ì ìš©ë˜ì—ˆìŠµë‹ˆë‹¤." + #: src/screens/Onboarding/Layout.tsx:60 msgid "The following steps will help customize your Bluesky experience." msgstr "ë‹¤ìŒ ë‹¨ê³„ëŠ” Bluesky í™˜ê²½ì„ ë§žì¶¤ ì„¤ì •í•˜ëŠ” ë° ë„ì›€ì´ ë©ë‹ˆë‹¤." -#: src/view/com/post-thread/PostThread.tsx:517 +#: src/view/com/post-thread/PostThread.tsx:518 msgid "The post may have been deleted." msgstr "ê²Œì‹œë¬¼ì´ ì‚ì œë˜ì—ˆì„ 수 있습니다." @@ -4074,20 +4208,21 @@ msgstr "서비스 ì´ìš©ì•½ê´€ì„ 다ìŒìœ¼ë¡œ ì´ë™í–ˆìŠµë‹ˆë‹¤:" msgid "There are many feeds to try:" msgstr "시ë„í•´ ë³¼ 만한 피드:" -#: src/view/screens/ProfileFeed.tsx:550 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:113 +#: src/view/screens/ProfileFeed.tsx:543 msgid "There was an an issue contacting the server, please check your internet connection and try again." msgstr "ì„œë²„ì— ì—°ê²°í•˜ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì¸í„°ë„· ì—°ê²°ì„ í™•ì¸í•œ 후 다시 시ë„하세요." -#: src/view/com/posts/FeedErrorMessage.tsx:139 +#: src/view/com/posts/FeedErrorMessage.tsx:138 msgid "There was an an issue removing this feed. Please check your internet connection and try again." msgstr "ì´ í”¼ë“œë¥¼ ì‚ì œí•˜ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì¸í„°ë„· ì—°ê²°ì„ í™•ì¸í•œ 후 다시 시ë„하세요." -#: src/view/screens/ProfileFeed.tsx:210 +#: src/view/screens/ProfileFeed.tsx:217 msgid "There was an an issue updating your feeds, please check your internet connection and try again." msgstr "피드를 ì—…ë°ì´íŠ¸í•˜ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì¸í„°ë„· ì—°ê²°ì„ í™•ì¸í•œ 후 다시 시ë„하세요." -#: src/view/screens/ProfileFeed.tsx:237 -#: src/view/screens/ProfileList.tsx:267 +#: src/view/screens/ProfileFeed.tsx:244 +#: src/view/screens/ProfileList.tsx:275 #: src/view/screens/SavedFeeds.tsx:209 #: src/view/screens/SavedFeeds.tsx:231 #: src/view/screens/SavedFeeds.tsx:252 @@ -4096,9 +4231,8 @@ msgstr "ì„œë²„ì— ì—°ê²°í•˜ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤" #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:57 #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:66 -#: src/view/com/feeds/FeedSourceCard.tsx:115 -#: src/view/com/feeds/FeedSourceCard.tsx:129 -#: src/view/com/feeds/FeedSourceCard.tsx:183 +#: src/view/com/feeds/FeedSourceCard.tsx:110 +#: src/view/com/feeds/FeedSourceCard.tsx:123 msgid "There was an issue contacting your server" msgstr "ì„œë²„ì— ì—°ê²°í•˜ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤" @@ -4106,7 +4240,7 @@ msgstr "ì„œë²„ì— ì—°ê²°í•˜ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤" msgid "There was an issue fetching notifications. Tap here to try again." msgstr "ì•Œë¦¼ì„ ê°€ì ¸ì˜¤ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì´ê³³ì„ íƒí•˜ì—¬ 다시 시ë„하세요." -#: src/view/com/posts/Feed.tsx:265 +#: src/view/com/posts/Feed.tsx:279 msgid "There was an issue fetching posts. Tap here to try again." msgstr "ê²Œì‹œë¬¼ì„ ê°€ì ¸ì˜¤ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì´ê³³ì„ íƒí•˜ì—¬ 다시 시ë„하세요." @@ -4119,34 +4253,40 @@ msgstr "리스트를 ê°€ì ¸ì˜¤ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì´ê³³ì„ msgid "There was an issue fetching your lists. Tap here to try again." msgstr "리스트를 ê°€ì ¸ì˜¤ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì´ê³³ì„ íƒí•˜ì—¬ 다시 시ë„하세요." -#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:63 -#: src/view/com/modals/ContentFilteringSettings.tsx:126 +#: src/components/ReportDialog/SubmitView.tsx:83 +msgid "There was an issue sending your report. Please check your internet connection." +msgstr "ì‹ ê³ ë¥¼ ì „ì†¡í•˜ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì¸í„°ë„· ì—°ê²°ì„ í™•ì¸í•´ 주세요." + +#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:65 msgid "There was an issue syncing your preferences with the server" msgstr "ì„¤ì •ì„ ì„œë²„ì™€ ë™ê¸°í™”하는 ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤" -#: src/view/screens/AppPasswords.tsx:66 +#: src/view/screens/AppPasswords.tsx:68 msgid "There was an issue with fetching your app passwords" msgstr "앱 비밀번호를 ê°€ì ¸ì˜¤ëŠ” ë™ì•ˆ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤" -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:93 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:105 -#: src/view/com/profile/ProfileHeader.tsx:157 -#: src/view/com/profile/ProfileHeader.tsx:178 -#: src/view/com/profile/ProfileHeader.tsx:217 -#: src/view/com/profile/ProfileHeader.tsx:230 -#: src/view/com/profile/ProfileHeader.tsx:250 -#: src/view/com/profile/ProfileHeader.tsx:272 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:98 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:120 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:134 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:96 +#: src/view/com/post-thread/PostThreadFollowBtn.tsx:108 +#: src/view/com/profile/ProfileMenu.tsx:106 +#: src/view/com/profile/ProfileMenu.tsx:117 +#: src/view/com/profile/ProfileMenu.tsx:132 +#: src/view/com/profile/ProfileMenu.tsx:143 +#: src/view/com/profile/ProfileMenu.tsx:157 +#: src/view/com/profile/ProfileMenu.tsx:170 msgid "There was an issue! {0}" msgstr "ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤! {0}" #: src/view/screens/ProfileList.tsx:288 -#: src/view/screens/ProfileList.tsx:307 -#: src/view/screens/ProfileList.tsx:329 -#: src/view/screens/ProfileList.tsx:348 +#: src/view/screens/ProfileList.tsx:302 +#: src/view/screens/ProfileList.tsx:316 +#: src/view/screens/ProfileList.tsx:330 msgid "There was an issue. Please check your internet connection and try again." msgstr "ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì¸í„°ë„· ì—°ê²°ì„ í™•ì¸í•œ 후 다시 시ë„하세요." -#: src/view/com/util/ErrorBoundary.tsx:36 +#: src/view/com/util/ErrorBoundary.tsx:51 msgid "There was an unexpected issue in the application. Please let us know if this happened to you!" msgstr "ì• í”Œë¦¬ì¼€ì´ì…˜ì— 예기치 ì•Šì€ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì´ëŸ° ì¼ì´ ë°œìƒí•˜ë©´ ì €í¬ì—게 ì•Œë ¤ì£¼ì„¸ìš”!" @@ -4154,27 +4294,36 @@ msgstr "ì• í”Œë¦¬ì¼€ì´ì…˜ì— 예기치 ì•Šì€ ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì´ msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." msgstr "Blueskyì— ì‹ ê·œ 사용ìžê°€ ëª°ë¦¬ê³ ìžˆìŠµë‹ˆë‹¤! 최대한 빨리 ê³„ì •ì„ í™œì„±í™”í•´ ë“œë¦¬ê² ìŠµë‹ˆë‹¤." -#: src/view/com/auth/create/Step2.tsx:55 -#~ msgid "There's something wrong with this number. Please choose your country and enter your full phone number!" -#~ msgstr "ìž˜ëª»ëœ ë²ˆí˜¸ìž…ë‹ˆë‹¤. êµê°€ë¥¼ ì„ íƒí•˜ê³ ì „ì²´ ì „í™”ë²ˆí˜¸ë¥¼ ìž…ë ¥í•˜ì„¸ìš”." - #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:138 msgid "These are popular accounts you might like:" msgstr "ë‚´ê°€ ì¢‹ì•„í• ë§Œí•œ ì¸ê¸° ê³„ì •ìž…ë‹ˆë‹¤:" -#: src/view/com/util/moderation/ScreenHider.tsx:88 +#: src/components/moderation/ScreenHider.tsx:116 msgid "This {screenDescription} has been flagged:" -msgstr "ì´ {screenDescription}ì— í”Œëž˜ê·¸ê°€ ì§€ì •ë˜ì—ˆìŠµë‹ˆë‹¤:" +msgstr "ì´ {screenDescription}ì— ë‹¤ìŒ í”Œëž˜ê·¸ê°€ ì§€ì •ë˜ì—ˆìŠµë‹ˆë‹¤:" -#: src/view/com/util/moderation/ScreenHider.tsx:83 +#: src/components/moderation/ScreenHider.tsx:111 msgid "This account has requested that users sign in to view their profile." msgstr "ì´ ê³„ì •ì˜ í”„ë¡œí•„ì„ ë³´ë ¤ë©´ 로그ì¸í•´ì•¼ 합니다." +#: src/components/moderation/LabelsOnMeDialog.tsx:205 +msgid "This appeal will be sent to <0>{0}</0>." +msgstr "ì´ ì´ì˜ì‹ ì²ì€ <0>{0}</0>ì—게 보내집니다." + +#: 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/view/com/modals/EmbedConsent.tsx:68 msgid "This content is hosted by {0}. Do you want to enable external media?" msgstr "ì´ ì½˜í…ì¸ ëŠ” {0}ì—서 호스팅ë©ë‹ˆë‹¤. 외부 미디어를 ì‚¬ìš©í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" -#: src/view/com/modals/ModerationDetails.tsx:67 +#: src/components/moderation/ModerationDetailsDialog.tsx:78 +#: src/lib/moderation/useModerationCauseDescription.ts:77 msgid "This content is not available because one of the users involved has blocked the other." msgstr "ê´€ë ¨ ì‚¬ìš©ìž ì¤‘ 한 ëª…ì´ ë‹¤ë¥¸ 사용ìžë¥¼ 차단했기 ë•Œë¬¸ì— ì´ ì½˜í…ì¸ ë¥¼ ì‚¬ìš©í• ìˆ˜ 없습니다." @@ -4184,15 +4333,15 @@ msgstr "ì´ ì½˜í…ì¸ ëŠ” Bluesky ê³„ì •ì´ ì—†ìœ¼ë©´ ë³¼ 수 없습니다." #: src/view/screens/Settings/ExportCarDialog.tsx:75 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost.</0>" -msgstr "" +msgstr "ì´ ê¸°ëŠ¥ì€ ë² íƒ€ ë²„ì „ìž…ë‹ˆë‹¤. ì €ìž¥ì†Œ ë‚´ë³´ë‚´ê¸°ì— ëŒ€í•œ ìžì„¸í•œ ë‚´ìš©ì€ <0>ì´ ë¸”ë¡œê·¸ 글</0>ì—서 확ì¸í• 수 있습니다." #: src/view/com/posts/FeedErrorMessage.tsx:114 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." msgstr "ì´ í”¼ë“œëŠ” 현재 íŠ¸ëž˜í”½ì´ ë§Žì•„ ì¼ì‹œì 으로 ì‚¬ìš©í• ìˆ˜ 없습니다. ë‚˜ì¤‘ì— ë‹¤ì‹œ 시ë„í•´ 주세요." -#: src/view/screens/Profile.tsx:420 +#: src/screens/Profile/Sections/Feed.tsx:50 #: src/view/screens/ProfileFeed.tsx:476 -#: src/view/screens/ProfileList.tsx:661 +#: src/view/screens/ProfileList.tsx:675 msgid "This feed is empty!" msgstr "ì´ í”¼ë“œëŠ” 비어 있습니다." @@ -4200,7 +4349,7 @@ msgstr "ì´ í”¼ë“œëŠ” 비어 있습니다." msgid "This feed is empty! You may need to follow more users or tune your language settings." msgstr "ì´ í”¼ë“œëŠ” 비어 있습니다. ë” ë§Žì€ ì‚¬ìš©ìžë¥¼ 팔로우하거나 언어 ì„¤ì •ì„ ì¡°ì •í•´ 보세요." -#: src/view/com/modals/BirthDateSettings.tsx:61 +#: src/components/dialogs/BirthDateSettings.tsx:89 msgid "This information is not shared with other users." msgstr "ì´ ì •ë³´ëŠ” 다른 사용ìžì™€ ê³µìœ ë˜ì§€ 않습니다." @@ -4208,14 +4357,26 @@ msgstr "ì´ ì •ë³´ëŠ” 다른 사용ìžì™€ ê³µìœ ë˜ì§€ 않습니다." msgid "This is important in case you ever need to change your email or reset your password." msgstr "ì´ëŠ” ì´ë©”ì¼ì„ 변경하거나 비밀번호를 ìž¬ì„¤ì •í•´ì•¼ í• ë•Œ 중요한 ì •ë³´ìž…ë‹ˆë‹¤." +#: src/components/moderation/ModerationDetailsDialog.tsx:125 +msgid "This label was applied by {0}." +msgstr "ì´ ë¼ë²¨ì€ {0}ì´(ê°€) ì 용했습니다." + +#: src/screens/Profile/Sections/Labels.tsx:186 +msgid "This labeler hasn't declared what labels it publishes, and may not be active." +msgstr "ì´ ë¼ë²¨ëŸ¬ëŠ” ë¼ë²¨ì„ 게시하지 않았으며 활성화ë˜ì–´ 있지 ì•Šì„ ìˆ˜ 있습니다." + #: src/view/com/modals/LinkWarning.tsx:58 msgid "This link is taking you to the following website:" msgstr "ì´ ë§í¬ë¥¼ í´ë¦í•˜ë©´ ë‹¤ìŒ ì›¹ì‚¬ì´íŠ¸ë¡œ ì´ë™í•©ë‹ˆë‹¤:" -#: src/view/screens/ProfileList.tsx:839 +#: src/view/screens/ProfileList.tsx:853 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:106 msgid "This name is already in use" msgstr "ì´ ì´ë¦„ì€ ì´ë¯¸ 사용 중입니다" @@ -4224,36 +4385,45 @@ msgstr "ì´ ì´ë¦„ì€ ì´ë¯¸ 사용 중입니다" msgid "This post has been deleted." msgstr "ì´ ê²Œì‹œë¬¼ì€ ì‚ì œë˜ì—ˆìŠµë‹ˆë‹¤." -#: src/view/com/modals/ModerationDetails.tsx:62 +#: src/view/com/util/forms/PostDropdownBtn.tsx:344 +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:326 +msgid "This post will be hidden from feeds." +msgstr "ì´ ê²Œì‹œë¬¼ì„ í”¼ë“œì—서 숨ê¹ë‹ˆë‹¤." + +#: src/view/com/profile/ProfileMenu.tsx:370 +msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't logged in." +msgstr "ì´ í”„ë¡œí•„ì€ ë¡œê·¸ì¸í•œ 사용ìžì—게만 표시ë©ë‹ˆë‹¤. 로그ì¸í•˜ì§€ ì•Šì€ ì‚¬ìš©ìžì—게는 표시ë˜ì§€ 않습니다." + +#: src/components/moderation/ModerationDetailsDialog.tsx:73 +#: src/lib/moderation/useModerationCauseDescription.ts:68 msgid "This user has blocked you. You cannot view their content." msgstr "ì´ ì‚¬ìš©ìžëŠ” 나를 차단했습니다. ì´ ì‚¬ìš©ìžì˜ 콘í…ì¸ ë¥¼ ë³¼ 수 없습니다." -#: src/view/com/modals/ModerationDetails.tsx:42 -msgid "This user is included in the <0/> list which you have blocked." -msgstr "ì´ ì‚¬ìš©ìžëŠ” 차단한 <0/> ë¦¬ìŠ¤íŠ¸ì— í¬í•¨ë˜ì–´ 있습니다." +#: src/lib/moderation/useGlobalLabelStrings.ts:30 +msgid "This user has requested that their content only be shown to signed-in users." +msgstr "ì´ ì‚¬ìš©ìžëŠ” ìžì‹ ì˜ ì½˜í…ì¸ ê°€ 로그ì¸í•œ 사용ìžì—게만 표시ë˜ë„ë¡ ìš”ì²í–ˆìŠµë‹ˆë‹¤." -#: src/view/com/modals/ModerationDetails.tsx:74 -msgid "This user is included in the <0/> list which you have muted." -msgstr "" +#: src/components/moderation/ModerationDetailsDialog.tsx:56 +msgid "This user is included in the <0>{0}</0> list which you have blocked." +msgstr "ì´ ì‚¬ìš©ìžëŠ” ë‚´ê°€ 차단한 <0>{0}</0> ë¦¬ìŠ¤íŠ¸ì— í¬í•¨ë˜ì–´ 있습니다." -#: src/view/com/modals/ModerationDetails.tsx:74 -#~ msgid "This user is included the <0/> list which you have muted." -#~ msgstr "ì´ ì‚¬ìš©ìžëŠ” 뮤트한 <0/> ë¦¬ìŠ¤íŠ¸ì— í¬í•¨ë˜ì–´ 있습니다." +#: src/components/moderation/ModerationDetailsDialog.tsx:85 +msgid "This user is included in the <0>{0}</0> list which you have muted." +msgstr "ì´ ì‚¬ìš©ìžëŠ” ë‚´ê°€ 뮤트한 <0>{0}</0> ë¦¬ìŠ¤íŠ¸ì— í¬í•¨ë˜ì–´ 있습니다." #: src/view/com/modals/SelfLabel.tsx:137 msgid "This warning is only available for posts with media attached." msgstr "ì´ ê²½ê³ ëŠ” 미디어가 ì²¨ë¶€ëœ ê²Œì‹œë¬¼ì—ë§Œ ì‚¬ìš©í• ìˆ˜ 있습니다." -#: src/components/dialogs/MutedWords.tsx:285 +#: src/components/dialogs/MutedWords.tsx:284 msgid "This will delete {0} from your muted words. You can always add it back later." -msgstr "" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:282 -msgid "This will hide this post from your feeds." -msgstr "피드ì—서 ì´ ê²Œì‹œë¬¼ì„ ìˆ¨ê¹ë‹ˆë‹¤." +msgstr "뮤트한 단어ì—서 {0}ì´(ê°€) ì‚ì œë©ë‹ˆë‹¤. ë‚˜ì¤‘ì— ì–¸ì œë“ ì§€ 다시 ì¶”ê°€í• ìˆ˜ 있습니다." #: src/view/screens/PreferencesThreads.tsx:53 -#: src/view/screens/Settings/index.tsx:565 +#: src/view/screens/Settings/index.tsx:583 msgid "Thread Preferences" msgstr "ìŠ¤ë ˆë“œ ì„¤ì •" @@ -4261,26 +4431,30 @@ msgstr "ìŠ¤ë ˆë“œ ì„¤ì •" msgid "Threaded Mode" msgstr "ìŠ¤ë ˆë“œ 모드" -#: src/Navigation.tsx:257 +#: src/Navigation.tsx:269 msgid "Threads Preferences" msgstr "ìŠ¤ë ˆë“œ ì„¤ì •" #: src/components/dialogs/MutedWords.tsx:113 msgid "Toggle between muted word options." -msgstr "" +msgstr "뮤트한 단어 옵션 사ì´ë¥¼ ì „í™˜í•©ë‹ˆë‹¤." #: src/view/com/util/forms/DropdownButton.tsx:246 msgid "Toggle dropdown" msgstr "드ë¡ë‹¤ìš´ 열기 ë° ë‹«ê¸°" +#: src/screens/Moderation/index.tsx:338 +msgid "Toggle to enable or disable adult content" +msgstr "ì„±ì¸ ì½˜í…ì¸ í™œì„±í™” ë˜ëŠ” 비활성화 ì „í™˜" + #: src/view/com/modals/EditImage.tsx:271 msgid "Transformations" msgstr "변형" -#: src/view/com/post-thread/PostThreadItem.tsx:685 -#: src/view/com/post-thread/PostThreadItem.tsx:687 -#: src/view/com/util/forms/PostDropdownBtn.tsx:215 -#: src/view/com/util/forms/PostDropdownBtn.tsx:217 +#: src/view/com/post-thread/PostThreadItem.tsx:646 +#: src/view/com/post-thread/PostThreadItem.tsx:648 +#: src/view/com/util/forms/PostDropdownBtn.tsx:212 +#: src/view/com/util/forms/PostDropdownBtn.tsx:214 msgid "Translate" msgstr "번ì—" @@ -4289,11 +4463,11 @@ msgctxt "action" msgid "Try again" msgstr "다시 시ë„" -#: src/view/screens/ProfileList.tsx:506 +#: src/view/screens/ProfileList.tsx:478 msgid "Un-block list" msgstr "리스트 차단 í•´ì œ" -#: src/view/screens/ProfileList.tsx:491 +#: src/view/screens/ProfileList.tsx:461 msgid "Un-mute list" msgstr "리스트 언뮤트" @@ -4305,21 +4479,28 @@ msgstr "리스트 언뮤트" msgid "Unable to contact your service. Please check your Internet connection." msgstr "ì„œë¹„ìŠ¤ì— ì—°ê²°í• ìˆ˜ 없습니다. ì¸í„°ë„· ì—°ê²°ì„ í™•ì¸í•˜ì„¸ìš”." -#: src/view/com/profile/ProfileHeader.tsx:433 -#: src/view/screens/ProfileList.tsx:590 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:174 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:278 +#: src/view/com/profile/ProfileMenu.tsx:361 +#: src/view/screens/ProfileList.tsx:572 msgid "Unblock" msgstr "차단 í•´ì œ" -#: src/view/com/profile/ProfileHeader.tsx:436 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:179 msgctxt "action" msgid "Unblock" msgstr "차단 í•´ì œ" -#: src/view/com/profile/ProfileHeader.tsx:261 -#: src/view/com/profile/ProfileHeader.tsx:345 +#: src/view/com/profile/ProfileMenu.tsx:299 +#: src/view/com/profile/ProfileMenu.tsx:305 msgid "Unblock Account" msgstr "ê³„ì • 차단 í•´ì œ" +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:272 +#: src/view/com/profile/ProfileMenu.tsx:343 +msgid "Unblock Account?" +msgstr "ê³„ì •ì„ ì°¨ë‹¨ í•´ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" + #: src/view/com/modals/Repost.tsx:42 #: src/view/com/modals/Repost.tsx:55 #: src/view/com/util/post-ctrls/RepostButton.tsx:60 @@ -4327,70 +4508,84 @@ msgstr "ê³„ì • 차단 í•´ì œ" msgid "Undo repost" msgstr "재게시 취소" -#: src/view/com/profile/FollowButton.tsx:55 +#: src/view/com/profile/FollowButton.tsx:60 msgctxt "action" msgid "Unfollow" msgstr "언팔로우" -#: src/view/com/profile/ProfileHeader.tsx:485 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:213 msgid "Unfollow {0}" msgstr "{0} ë‹˜ì„ ì–¸íŒ”ë¡œìš°" +#: src/view/com/profile/ProfileMenu.tsx:241 +#: src/view/com/profile/ProfileMenu.tsx:251 +msgid "Unfollow Account" +msgstr "ê³„ì • 언팔로우" + #: src/view/com/auth/create/state.ts:262 msgid "Unfortunately, you do not meet the requirements to create an account." msgstr "아쉽지만 ê³„ì •ì„ ë§Œë“¤ 수 있는 ìš”ê±´ì„ ì¶©ì¡±í•˜ì§€ 못했습니다." -#: src/view/com/util/post-ctrls/PostCtrls.tsx:182 +#: src/view/com/util/post-ctrls/PostCtrls.tsx:185 msgid "Unlike" msgstr "좋아요 취소" +#: src/view/screens/ProfileFeed.tsx:572 +msgid "Unlike this feed" +msgstr "ì´ í”¼ë“œ 좋아요 취소" + #: src/components/TagMenu/index.tsx:249 -#: src/view/screens/ProfileList.tsx:597 +#: src/view/screens/ProfileList.tsx:579 msgid "Unmute" msgstr "언뮤트" #: src/components/TagMenu/index.web.tsx:104 msgid "Unmute {truncatedTag}" -msgstr "" +msgstr "{truncatedTag} 언뮤트" -#: src/view/com/profile/ProfileHeader.tsx:326 +#: src/view/com/profile/ProfileMenu.tsx:278 +#: src/view/com/profile/ProfileMenu.tsx:284 msgid "Unmute Account" msgstr "ê³„ì • 언뮤트" #: src/components/TagMenu/index.tsx:208 msgid "Unmute all {displayTag} posts" -msgstr "" - -#: src/components/TagMenu/index.tsx:210 -#~ msgid "Unmute all {tag} posts" -#~ msgstr "" +msgstr "ëª¨ë“ {tag} 게시물 언뮤트" #: src/view/com/util/forms/PostDropdownBtn.tsx:251 #: src/view/com/util/forms/PostDropdownBtn.tsx:256 msgid "Unmute thread" msgstr "ìŠ¤ë ˆë“œ 언뮤트" -#: src/view/screens/ProfileFeed.tsx:354 -#: src/view/screens/ProfileList.tsx:581 +#: src/view/screens/ProfileFeed.tsx:294 +#: src/view/screens/ProfileList.tsx:563 msgid "Unpin" msgstr "ê³ ì • í•´ì œ" -#: src/view/screens/ProfileList.tsx:474 +#: src/view/screens/ProfileFeed.tsx:291 +msgid "Unpin from home" +msgstr "홈ì—서 ê³ ì • í•´ì œ" + +#: src/view/screens/ProfileList.tsx:444 msgid "Unpin moderation list" msgstr "ê²€í† ë¦¬ìŠ¤íŠ¸ ê³ ì • í•´ì œ" -#: src/view/screens/ProfileFeed.tsx:346 -msgid "Unsave" -msgstr "ì €ìž¥ í•´ì œ" +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:220 +msgid "Unsubscribe" +msgstr "êµ¬ë… ì·¨ì†Œ" + +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:184 +msgid "Unsubscribe from this labeler" +msgstr "ì´ ë¼ë²¨ëŸ¬ êµ¬ë… ì·¨ì†Œí•˜ê¸°" + +#: src/lib/moderation/useReportOptions.ts:70 +msgid "Unwanted Sexual Content" +msgstr "ì›ì¹˜ 않는 성ì 콘í…ì¸ " #: src/view/com/modals/UserAddRemoveLists.tsx:70 msgid "Update {displayName} in Lists" msgstr "리스트ì—서 {displayName} ì—…ë°ì´íЏ" -#: src/lib/hooks/useOTAUpdate.ts:15 -msgid "Update Available" -msgstr "ì—…ë°ì´íЏ 사용 가능" - #: src/view/com/auth/login/SetNewPasswordForm.tsx:204 msgid "Updating..." msgstr "ì—…ë°ì´íЏ 중…" @@ -4399,7 +4594,26 @@ msgstr "ì—…ë°ì´íЏ 중…" msgid "Upload a text file to:" msgstr "í…스트 íŒŒì¼ ì—…ë¡œë“œ 경로:" -#: src/view/screens/AppPasswords.tsx:195 +#: src/view/com/util/UserAvatar.tsx:319 +#: src/view/com/util/UserAvatar.tsx:322 +#: src/view/com/util/UserBanner.tsx:113 +#: src/view/com/util/UserBanner.tsx:116 +msgid "Upload from Camera" +msgstr "ì¹´ë©”ë¼ì—서 업로드" + +#: src/view/com/util/UserAvatar.tsx:336 +#: src/view/com/util/UserBanner.tsx:130 +msgid "Upload from Files" +msgstr "파ì¼ì—서 업로드" + +#: src/view/com/util/UserAvatar.tsx:330 +#: src/view/com/util/UserAvatar.tsx:334 +#: src/view/com/util/UserBanner.tsx:124 +#: src/view/com/util/UserBanner.tsx:128 +msgid "Upload from Library" +msgstr "ë¼ì´ë¸ŒëŸ¬ë¦¬ì—서 업로드" + +#: src/view/screens/AppPasswords.tsx:197 msgid "Use app passwords to login to other Bluesky clients without giving full access to your account or password." msgstr "앱 비밀번호를 사용하면 ê³„ì •ì´ë‚˜ ë¹„ë°€ë²ˆí˜¸ì— ëŒ€í•œ ì „ì²´ ì ‘ê·¼ ê¶Œí•œì„ ì œê³µí•˜ì§€ ì•Šê³ ë„ ë‹¤ë¥¸ Bluesky í´ë¼ì´ì–¸íŠ¸ì— ë¡œê·¸ì¸í• 수 있습니다." @@ -4421,25 +4635,30 @@ msgstr "ë‚´ 기본 브ë¼ìš°ì € 사용" msgid "Use this to sign into the other app along with your handle." msgstr "ì´ ë¹„ë°€ë²ˆí˜¸ì™€ í•¸ë“¤ì„ ì‚¬ìš©í•˜ì—¬ 다른 ì•±ì— ë¡œê·¸ì¸í•˜ì„¸ìš”." -#: src/view/com/modals/ServerInput.tsx:105 -#~ msgid "Use your domain as your Bluesky client service provider" -#~ msgstr "ë‚´ ë„ë©”ì¸ì„ Bluesky í´ë¼ì´ì–¸íЏ 서비스 공급ìžë¡œ 사용합니다" - #: src/view/com/modals/InviteCodes.tsx:200 msgid "Used by:" msgstr "사용 ê³„ì •:" -#: src/view/com/modals/ModerationDetails.tsx:54 +#: src/components/moderation/ModerationDetailsDialog.tsx:65 +#: src/lib/moderation/useModerationCauseDescription.ts:56 msgid "User Blocked" msgstr "ì‚¬ìš©ìž ì°¨ë‹¨ë¨" -#: src/view/com/modals/ModerationDetails.tsx:40 +#: src/lib/moderation/useModerationCauseDescription.ts:48 +msgid "User Blocked by \"{0}\"" +msgstr " \"{0}\"ì—서 ì°¨ë‹¨ëœ ì‚¬ìš©ìž" + +#: src/components/moderation/ModerationDetailsDialog.tsx:54 msgid "User Blocked by List" msgstr "리스트로 ì‚¬ìš©ìž ì°¨ë‹¨ë¨" -#: src/view/com/modals/ModerationDetails.tsx:60 +#: src/lib/moderation/useModerationCauseDescription.ts:66 +msgid "User Blocking You" +msgstr "나를 차단한 사용ìž" + +#: src/components/moderation/ModerationDetailsDialog.tsx:71 msgid "User Blocks You" -msgstr "사용ìžê°€ 나를 차단함" +msgstr "나를 차단한 사용ìž" #: src/view/com/auth/create/Step2.tsx:79 msgid "User handle" @@ -4450,13 +4669,13 @@ msgstr "ì‚¬ìš©ìž í•¸ë“¤" msgid "User list by {0}" msgstr "{0} ë‹˜ì˜ ì‚¬ìš©ìž ë¦¬ìŠ¤íŠ¸" -#: src/view/screens/ProfileList.tsx:763 +#: src/view/screens/ProfileList.tsx:777 msgid "User list by <0/>" msgstr "<0/> ë‹˜ì˜ ì‚¬ìš©ìž ë¦¬ìŠ¤íŠ¸" #: src/view/com/lists/ListCard.tsx:83 #: src/view/com/modals/UserAddRemoveLists.tsx:196 -#: src/view/screens/ProfileList.tsx:761 +#: src/view/screens/ProfileList.tsx:775 msgid "User list by you" msgstr "ë‚´ ì‚¬ìš©ìž ë¦¬ìŠ¤íŠ¸" @@ -4477,7 +4696,7 @@ msgstr "ì‚¬ìš©ìž ë¦¬ìŠ¤íŠ¸" msgid "Username or email address" msgstr "ì‚¬ìš©ìž ì´ë¦„ ë˜ëŠ” ì´ë©”ì¼ ì£¼ì†Œ" -#: src/view/screens/ProfileList.tsx:797 +#: src/view/screens/ProfileList.tsx:811 msgid "Users" msgstr "사용ìž" @@ -4489,19 +4708,19 @@ msgstr "<0/> ë‹˜ì´ íŒ”ë¡œìš°í•œ 사용ìž" msgid "Users in \"{0}\"" msgstr "\"{0}\"ì— ìžˆëŠ” 사용ìž" -#: src/view/com/auth/create/Step2.tsx:243 -#~ msgid "Verification code" -#~ msgstr "ì¸ì¦ 코드" +#: src/components/LikesDialog.tsx:85 +msgid "Users that have liked this content or profile" +msgstr "ì´ ì½˜í…ì¸ ë˜ëŠ” í”„ë¡œí•„ì„ ì¢‹ì•„í•˜ëŠ” 사용ìž" -#: src/view/screens/Settings/index.tsx:910 +#: src/view/screens/Settings/index.tsx:938 msgid "Verify email" msgstr "ì´ë©”ì¼ ì¸ì¦" -#: src/view/screens/Settings/index.tsx:935 +#: src/view/screens/Settings/index.tsx:963 msgid "Verify my email" msgstr "ë‚´ ì´ë©”ì¼ ì¸ì¦í•˜ê¸°" -#: src/view/screens/Settings/index.tsx:944 +#: src/view/screens/Settings/index.tsx:972 msgid "Verify My Email" msgstr "ë‚´ ì´ë©”ì¼ ì¸ì¦í•˜ê¸°" @@ -4518,7 +4737,7 @@ msgstr "ì´ë©”ì¼ ì¸ì¦í•˜ê¸°" msgid "Video Games" msgstr "비디오 게임" -#: src/view/com/profile/ProfileHeader.tsx:662 +#: src/screens/Profile/Header/Shell.tsx:110 msgid "View {0}'s avatar" msgstr "{0} ë‹˜ì˜ ì•„ë°”íƒ€ë¥¼ 봅니다" @@ -4526,11 +4745,23 @@ msgstr "{0} ë‹˜ì˜ ì•„ë°”íƒ€ë¥¼ 봅니다" msgid "View debug entry" msgstr "디버그 í•목 보기" -#: src/view/com/posts/FeedSlice.tsx:103 +#: src/components/ReportDialog/SelectReportOptionView.tsx:132 +msgid "View details" +msgstr "세부 ì •ë³´ 보기" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:127 +msgid "View details for reporting a copyright violation" +msgstr "ì €ìž‘ê¶Œ 위반 ì‹ ê³ ì— ëŒ€í•œ 세부 ì •ë³´ 보기" + +#: src/view/com/posts/FeedSlice.tsx:99 msgid "View full thread" msgstr "ì „ì²´ ìŠ¤ë ˆë“œ 보기" -#: src/view/com/posts/FeedErrorMessage.tsx:172 +#: src/components/moderation/LabelsOnMe.tsx:51 +msgid "View information about these labels" +msgstr "ì´ ë¼ë²¨ì— 대한 ì •ë³´ 보기" + +#: src/view/com/posts/FeedErrorMessage.tsx:166 msgid "View profile" msgstr "프로필 보기" @@ -4538,22 +4769,40 @@ msgstr "프로필 보기" msgid "View the avatar" msgstr "아바타 보기" +#: src/components/LabelingServiceCard/index.tsx:140 +msgid "View the labeling service provided by @{0}" +msgstr "{0} ë‹˜ì´ ì œê³µí•˜ëŠ” ë¼ë²¨ë§ 서비스 보기" + +#: src/view/screens/ProfileFeed.tsx:584 +msgid "View users who like this feed" +msgstr "ì´ í”¼ë“œë¥¼ 좋아하는 ì‚¬ìš©ìž ë³´ê¸°" + #: src/view/com/modals/LinkWarning.tsx:75 msgid "Visit Site" msgstr "사ì´íЏ 방문" -#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:42 -#: src/view/com/modals/ContentFilteringSettings.tsx:259 +#: src/components/moderation/GlobalModerationLabelPref.tsx:44 +#: src/lib/moderation/useLabelBehaviorDescription.ts:17 +#: src/lib/moderation/useLabelBehaviorDescription.ts:22 +#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:53 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/Onboarding/StepAlgoFeeds/index.tsx:134 msgid "We also think you'll like \"For You\" by Skygaze:" msgstr "Skygazeì˜ \"For You\"를 사용해 ë³¼ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤:" #: src/screens/Hashtag.tsx:132 msgid "We couldn't find any results for that hashtag." -msgstr "" +msgstr "해당 í•´ì‹œíƒœê·¸ì— ëŒ€í•œ 결과를 ì°¾ì„ ìˆ˜ 없습니다." #: src/screens/Deactivated.tsx:133 msgid "We estimate {estimatedTime} until your account is ready." @@ -4569,12 +4818,16 @@ msgstr "팔로우한 사용ìžì˜ ê²Œì‹œë¬¼ì´ ë¶€ì¡±í•©ë‹ˆë‹¤. ëŒ€ì‹ <0/>ì˜ ì #: src/components/dialogs/MutedWords.tsx:204 msgid "We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." -msgstr "" +msgstr "ê²Œì‹œë¬¼ì´ í‘œì‹œë˜ì§€ ì•Šì„ ìˆ˜ 있으므로 ë§Žì€ ê²Œì‹œë¬¼ì— ìžì£¼ 등장하는 단어는 피하는 ê²ƒì´ ì¢‹ìŠµë‹ˆë‹¤." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124 msgid "We recommend our \"Discover\" feed:" msgstr "\"Discover\" 피드를 권장합니다:" +#: src/screens/Moderation/index.tsx:391 +msgid "We were unable to load your configured labelers at this time." +msgstr "현재 êµ¬ì„±ëœ ë¼ë²¨ëŸ¬ë¥¼ 불러올 수 없습니다." + #: src/screens/Onboarding/StepInterests/index.tsx:133 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 "연결하지 못했습니다. ê³„ì • ì„¤ì •ì„ ê³„ì†í•˜ë ¤ë©´ 다시 시ë„í•´ 주세요. ê³„ì† ì‹¤íŒ¨í•˜ë©´ ì´ ê³¼ì •ì„ ê±´ë„ˆë›¸ 수 있습니다." @@ -4583,49 +4836,45 @@ msgstr "연결하지 못했습니다. ê³„ì • ì„¤ì •ì„ ê³„ì†í•˜ë ¤ë©´ 다시 ì‹ msgid "We will let you know when your account is ready." msgstr "ê³„ì •ì´ ì¤€ë¹„ë˜ë©´ ì•Œë ¤ë“œë¦¬ê² ìŠµë‹ˆë‹¤." -#: src/view/com/modals/AppealLabel.tsx:48 -msgid "We'll look into your appeal promptly." -msgstr "ì´ì˜ì‹ ì²ì„ 즉시 ê²€í† í•˜ê² ìŠµë‹ˆë‹¤." - #: src/screens/Onboarding/StepInterests/index.tsx:138 msgid "We'll use this to help customize your experience." msgstr "ì´ë¥¼ 통해 ì‚¬ìš©ìž í™˜ê²½ì„ ë§žì¶¤ ì„¤ì •í• ìˆ˜ 있습니다." #: src/view/com/auth/create/CreateAccount.tsx:134 msgid "We're so excited to have you join us!" -msgstr "ë‹¹ì‹ ê³¼ 함께하게 ë˜ì–´ ì •ë§ ê¸°ì˜ë„¤ìš”!" +msgstr "함께하게 ë˜ì–´ ì •ë§ ê¸°ë»ìš”!" -#: src/view/screens/ProfileList.tsx:86 +#: src/view/screens/ProfileList.tsx:89 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:230 msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." -msgstr "" +msgstr "죄송하지만 현재 뮤트한 단어를 불러올 수 없습니다. 다시 시ë„í•´ 주세요." #: src/view/screens/Search/Search.tsx:254 msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." msgstr "죄송하지만 ê²€ìƒ‰ì„ ì™„ë£Œí• ìˆ˜ 없습니다. 몇 ë¶„ í›„ì— ë‹¤ì‹œ 시ë„í•´ 주세요." -#: src/components/Lists.tsx:211 +#: src/components/Lists.tsx:210 #: 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:319 +msgid "We're sorry! You can only subscribe to ten labelers, and you've reached your limit of ten." +msgstr "죄송합니다. ë¼ë²¨ëŸ¬ëŠ” 10개까지만 구ë…í• ìˆ˜ 있으며 10ê°œì— ë„달했습니다." + #: src/view/com/auth/onboarding/WelcomeMobile.tsx:46 msgid "Welcome to <0>Bluesky</0>" msgstr "<0>Bluesky</0>ì— ì˜¤ì‹ ê²ƒì„ í™˜ì˜í•©ë‹ˆë‹¤" #: src/screens/Onboarding/StepInterests/index.tsx:130 msgid "What are your interests?" -msgstr "관심사가 어떻게 ë˜ë‚˜ìš”?" - -#: src/view/com/modals/report/Modal.tsx:169 -msgid "What is the issue with this {collectionName}?" -msgstr "ì´ {collectionName}ì— ì–´ë–¤ ë¬¸ì œê°€ 있나요?" +msgstr "ì–´ë–¤ 관심사가 ìžˆìœ¼ì‹ ê°€ìš”?" #: src/view/com/auth/SplashScreen.tsx:59 -#: src/view/com/composer/Composer.tsx:286 +#: src/view/com/composer/Composer.tsx:295 msgid "What's up?" msgstr "무슨 ì¼ì´ ì¼ì–´ë‚˜ê³ 있나요?" @@ -4642,15 +4891,39 @@ msgstr "ì•Œê³ ë¦¬ì¦˜ í”¼ë“œì— ì–´ë–¤ 언어를 í‘œì‹œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" msgid "Who can reply" msgstr "ë‹µê¸€ì„ ë‹¬ 수 있는 사람" +#: src/components/ReportDialog/SelectLabelerView.tsx:33 +msgid "Who do you want to send this report to?" +msgstr "ì´ ì‹ ê³ ë¥¼ 누구ì—게 ë³´ë‚´ì‹œê² ìŠµë‹ˆê¹Œ?" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:43 +msgid "Why should this content be reviewed?" +msgstr "ì´ ì½˜í…ì¸ ë¥¼ ê²€í† í•´ì•¼ 하는 ì´ìœ 는 무엇ì¸ê°€ìš”?" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:56 +msgid "Why should this feed be reviewed?" +msgstr "ì´ í”¼ë“œë¥¼ ê²€í† í•´ì•¼ 하는 ì´ìœ 는 무엇ì¸ê°€ìš”?" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:53 +msgid "Why should this list be reviewed?" +msgstr "ì´ ë¦¬ìŠ¤íŠ¸ë¥¼ ê²€í† í•´ì•¼ 하는 ì´ìœ 는 무엇ì¸ê°€ìš”?" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:50 +msgid "Why should this post be reviewed?" +msgstr "ì´ ê²Œì‹œë¬¼ì„ ê²€í† í•´ì•¼ 하는 ì´ìœ 는 무엇ì¸ê°€ìš”?" + +#: src/components/ReportDialog/SelectReportOptionView.tsx:47 +msgid "Why should this user be reviewed?" +msgstr "ì´ ì‚¬ìš©ìžë¥¼ ê²€í† í•´ì•¼ 하는 ì´ìœ 는 무엇ì¸ê°€ìš”?" + #: src/view/com/modals/crop-image/CropImage.web.tsx:102 msgid "Wide" msgstr "가로" -#: src/view/com/composer/Composer.tsx:422 +#: src/view/com/composer/Composer.tsx:431 msgid "Write post" msgstr "게시물 작성" -#: src/view/com/composer/Composer.tsx:285 +#: src/view/com/composer/Composer.tsx:294 #: src/view/com/composer/Prompt.tsx:33 msgid "Write your reply" msgstr "답글 작성하기" @@ -4659,10 +4932,6 @@ msgstr "답글 작성하기" msgid "Writers" msgstr "작가" -#: src/view/com/auth/create/Step2.tsx:263 -#~ msgid "XXXXXX" -#~ msgstr "XXXXXX" - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:77 #: src/view/screens/PreferencesFollowingFeed.tsx:129 #: src/view/screens/PreferencesFollowingFeed.tsx:201 @@ -4707,11 +4976,13 @@ msgstr "ì €ìž¥ëœ í”¼ë“œê°€ 없습니다!" msgid "You don't have any saved feeds." msgstr "ì €ìž¥ëœ í”¼ë“œê°€ 없습니다." -#: src/view/com/post-thread/PostThread.tsx:465 +#: src/view/com/post-thread/PostThread.tsx:466 msgid "You have blocked the author or you have been blocked by the author." msgstr "작성ìžë¥¼ 차단했거나 작성ìžê°€ 나를 차단했습니다." -#: src/view/com/modals/ModerationDetails.tsx:56 +#: src/components/moderation/ModerationDetailsDialog.tsx:67 +#: src/lib/moderation/useModerationCauseDescription.ts:50 +#: src/lib/moderation/useModerationCauseDescription.ts:58 msgid "You have blocked this user. You cannot view their content." msgstr "ì´ ì‚¬ìš©ìžë¥¼ 차단했습니다. 해당 사용ìžì˜ 콘í…ì¸ ë¥¼ ë³¼ 수 없습니다." @@ -4720,11 +4991,24 @@ msgstr "ì´ ì‚¬ìš©ìžë¥¼ 차단했습니다. 해당 사용ìžì˜ 콘í…ì¸ ë¥¼ ë³ #: src/view/com/modals/ChangePassword.tsx:87 #: src/view/com/modals/ChangePassword.tsx:121 msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." -msgstr "" +msgstr "ìž˜ëª»ëœ ì½”ë“œë¥¼ ìž…ë ¥í–ˆìŠµë‹ˆë‹¤. XXXXX-XXXXX와 ê°™ì€ í˜•ì‹ì´ì–´ì•¼ 합니다." + +#: src/lib/moderation/useModerationCauseDescription.ts:109 +msgid "You have hidden this post" +msgstr "ë‚´ê°€ ì´ ê²Œì‹œë¬¼ì„ ìˆ¨ê²¼ìŠµë‹ˆë‹¤" -#: src/view/com/modals/ModerationDetails.tsx:87 -msgid "You have muted this user." -msgstr "ì´ ì‚¬ìš©ìžë¥¼ 뮤트했습니다." +#: src/components/moderation/ModerationDetailsDialog.tsx:102 +msgid "You have hidden this post." +msgstr "ë‚´ê°€ ì´ ê²Œì‹œë¬¼ì„ ìˆ¨ê²¼ìŠµë‹ˆë‹¤." + +#: src/components/moderation/ModerationDetailsDialog.tsx:95 +#: src/lib/moderation/useModerationCauseDescription.ts:92 +msgid "You have muted this account." +msgstr "ë‚´ê°€ ì´ ê³„ì •ì„ ë®¤íŠ¸í–ˆìŠµë‹ˆë‹¤." + +#: src/lib/moderation/useModerationCauseDescription.ts:86 +msgid "You have muted this user" +msgstr "ë‚´ê°€ ì´ ì‚¬ìš©ìžë¥¼ 뮤트했습니다" #: src/view/com/feeds/ProfileFeedgens.tsx:136 msgid "You have no feeds." @@ -4739,7 +5023,7 @@ msgstr "리스트가 없습니다." msgid "You have not blocked any accounts yet. To block an account, go to their profile and selected \"Block account\" from the menu on their account." msgstr "ì•„ì§ ì–´ë–¤ ê³„ì •ë„ ì°¨ë‹¨í•˜ì§€ 않았습니다. ê³„ì •ì„ ì°¨ë‹¨í•˜ë ¤ë©´ 해당 ê³„ì •ì˜ í”„ë¡œí•„ë¡œ ì´ë™í•˜ì—¬ ê³„ì • 메뉴ì—서 \"ê³„ì • 차단\"ì„ ì„ íƒí•˜ì„¸ìš”." -#: src/view/screens/AppPasswords.tsx:87 +#: src/view/screens/AppPasswords.tsx:89 msgid "You have not created any app passwords yet. You can create one by pressing the button below." msgstr "ì•„ì§ ì•± 비밀번호를 ìƒì„±í•˜ì§€ 않았습니다. 아래 ë²„íŠ¼ì„ ëˆŒëŸ¬ ìƒì„±í• 수 있습니다." @@ -4749,21 +5033,25 @@ msgstr "ì•„ì§ ì–´ë–¤ ê³„ì •ë„ ë®¤íŠ¸í•˜ì§€ 않았습니다. ê³„ì •ì„ ë®¤íŠ¸í• #: src/components/dialogs/MutedWords.tsx:250 msgid "You haven't muted any words or tags yet" -msgstr "" +msgstr "ì•„ì§ ì–´ë–¤ 단어나 íƒœê·¸ë„ ë®¤íŠ¸í•˜ì§€ 않았습니다" -#: src/view/com/modals/ContentFilteringSettings.tsx:175 -msgid "You must be 18 or older to enable adult content." -msgstr "ì„±ì¸ ì½˜í…ì¸ ë¥¼ í™œì„±í™”í•˜ë ¤ë©´ 18세 ì´ìƒì´ì–´ì•¼ 합니다." +#: src/components/moderation/LabelsOnMeDialog.tsx:69 +msgid "You may appeal these labels if you feel they were placed in error." +msgstr "ì´ ë¼ë²¨ì´ 잘못 ì§€ì •ë˜ì—ˆë‹¤ê³ ìƒê°ë˜ë©´ ì´ì˜ì‹ ì²í• 수 있습니다." -#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:103 +#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:108 msgid "You must be 18 years or older to enable adult content" msgstr "ì„±ì¸ ì½˜í…ì¸ ë¥¼ ì‚¬ìš©í•˜ë ¤ë©´ ë§Œ 18세 ì´ìƒì´ì–´ì•¼ 합니다." -#: src/view/com/util/forms/PostDropdownBtn.tsx:147 +#: src/components/ReportDialog/SubmitView.tsx:205 +msgid "You must select at least one labeler for a report" +msgstr "ì‹ ê³ í•˜ë ¤ë©´ 하나 ì´ìƒì˜ ë¼ë²¨ì„ ì„ íƒí•´ì•¼ 합니다." + +#: src/view/com/util/forms/PostDropdownBtn.tsx:144 msgid "You will no longer receive notifications for this thread" msgstr "ì´ ìŠ¤ë ˆë“œì— ëŒ€í•œ ì•Œë¦¼ì„ ë” ì´ìƒ 받지 않습니다" -#: src/view/com/util/forms/PostDropdownBtn.tsx:150 +#: src/view/com/util/forms/PostDropdownBtn.tsx:147 msgid "You will now receive notifications for this thread" msgstr "ì´ì œ ì´ ìŠ¤ë ˆë“œì— ëŒ€í•œ ì•Œë¦¼ì„ ë°›ìŠµë‹ˆë‹¤" @@ -4771,7 +5059,7 @@ msgstr "ì´ì œ ì´ ìŠ¤ë ˆë“œì— ëŒ€í•œ ì•Œë¦¼ì„ ë°›ìŠµë‹ˆë‹¤" msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password." msgstr "\"ìž¬ì„¤ì • 코드\"ê°€ í¬í•¨ëœ ì´ë©”ì¼ì„ 받게 ë˜ë©´ ì—¬ê¸°ì— í•´ë‹¹ 코드를 ìž…ë ¥í•œ ë‹¤ìŒ ìƒˆ 비밀번호를 ìž…ë ¥í•©ë‹ˆë‹¤." -#: src/screens/Onboarding/StepModeration/index.tsx:72 +#: src/screens/Onboarding/StepModeration/index.tsx:59 msgid "You're in control" msgstr "ì§ì ‘ ì œì–´í•˜ì„¸ìš”" @@ -4785,6 +5073,11 @@ msgstr "대기 중입니다" msgid "You're ready to go!" msgstr "준비가 ë났습니다!" +#: src/components/moderation/ModerationDetailsDialog.tsx:99 +#: src/lib/moderation/useModerationCauseDescription.ts:101 +msgid "You've chosen to hide a word or tag within this post." +msgstr "ì´ ê¸€ì—서 단어 ë˜ëŠ” 태그를 숨기ë„ë¡ ì„¤ì •í–ˆìŠµë‹ˆë‹¤." + #: src/view/com/posts/FollowingEndOfFeed.tsx:48 msgid "You've reached the end of your feed! Find some more accounts to follow." msgstr "피드 ëì— ë„달했습니다! íŒ”ë¡œìš°í• ê³„ì •ì„ ë” ì°¾ì•„ë³´ì„¸ìš”." @@ -4799,7 +5092,7 @@ msgstr "ê³„ì •ì„ ì‚ì œí–ˆìŠµë‹ˆë‹¤" #: src/view/screens/Settings/ExportCarDialog.tsx:47 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 "" +msgstr "ëª¨ë“ ê³µê°œ ë°ì´í„° ë ˆì½”ë“œê°€ í¬í•¨ëœ ê³„ì • ì €ìž¥ì†Œë¥¼ \"CAR\" 파ì¼ë¡œ ë‹¤ìš´ë¡œë“œí• ìˆ˜ 있습니다. ì´ íŒŒì¼ì—는 ì´ë¯¸ì§€ì™€ ê°™ì€ ë¯¸ë””ì–´ ìž„ë² ë“œë‚˜ 별ë„로 ê°€ì ¸ì™€ì•¼ 하는 비공개 ë°ì´í„°ëŠ” í¬í•¨ë˜ì§€ 않습니다." #: src/view/com/auth/create/Step1.tsx:215 msgid "Your birth date" @@ -4819,10 +5112,6 @@ msgstr "기본 피드는 \"팔로우 중\"입니다" msgid "Your email appears to be invalid." msgstr "ì´ë©”ì¼ì´ ìž˜ëª»ëœ ê²ƒ 같습니다." -#: src/view/com/modals/Waitlist.tsx:109 -#~ msgid "Your email has been saved! We'll be in touch soon." -#~ msgstr "ì´ë©”ì¼ì´ ì €ìž¥ë˜ì—ˆìŠµë‹ˆë‹¤! 가까운 ì‹œì¼ ë‚´ì— ì—°ë½ë“œë¦¬ê² 습니다." - #: src/view/com/modals/ChangeEmail.tsx:125 msgid "Your email has been updated but not verified. As a next step, please verify your new email." msgstr "ì´ë©”ì¼ì´ 변경ë˜ì—ˆì§€ë§Œ ì¸ì¦ë˜ì§€ 않았습니다. ë‹¤ìŒ ë‹¨ê³„ë¡œ 새 ì´ë©”ì¼ì„ ì¸ì¦í•´ 주세요." @@ -4833,7 +5122,7 @@ msgstr "ì´ë©”ì¼ì´ ì•„ì§ ì¸ì¦ë˜ì§€ 않았습니다. ì´ëŠ” 중요한 ë³´ì• #: src/view/com/posts/FollowingEmptyState.tsx:47 msgid "Your following feed is empty! Follow more users to see what's happening." -msgstr "팔로우 ì¤‘ì¸ í”¼ë“œê°€ 비어 있습니다! ë” ë§Žì€ ì‚¬ìš©ìžë¥¼ 팔로우하여 무슨 ì¼ì´ ì¼ì–´ë‚˜ê³ 있는지 확ì¸í•˜ì„¸ìš”." +msgstr "팔로우 중 피드가 비어 있습니다! ë” ë§Žì€ ì‚¬ìš©ìžë¥¼ 팔로우하여 무슨 ì¼ì´ ì¼ì–´ë‚˜ê³ 있는지 확ì¸í•˜ì„¸ìš”." #: src/view/com/auth/create/Step2.tsx:83 msgid "Your full handle will be" @@ -4843,21 +5132,15 @@ msgstr "ë‚´ ì „ì²´ 핸들:" msgid "Your full handle will be <0>@{0}</0>" msgstr "ë‚´ ì „ì²´ 핸들: <0>@{0}</0>" -#: src/view/screens/Settings.tsx:430 -#: src/view/shell/desktop/RightNav.tsx:137 -#: src/view/shell/Drawer.tsx:660 -#~ msgid "Your invite codes are hidden when logged in using an App Password" -#~ msgstr "앱 비밀번호를 사용하여 로그ì¸í•˜ë©´ 초대 코드가 숨겨집니다" - #: src/components/dialogs/MutedWords.tsx:221 msgid "Your muted words" -msgstr "" +msgstr "뮤트한 단어" #: src/view/com/modals/ChangePassword.tsx:155 msgid "Your password has been changed successfully!" -msgstr "" +msgstr "비밀번호를 성공ì 으로 변경했습니다." -#: src/view/com/composer/Composer.tsx:274 +#: src/view/com/composer/Composer.tsx:283 msgid "Your post has been published" msgstr "ê²Œì‹œë¬¼ì„ ê²Œì‹œí–ˆìŠµë‹ˆë‹¤" @@ -4868,11 +5151,11 @@ msgid "Your posts, likes, and blocks are public. Mutes are private." msgstr "게시물, 좋아요, 차단 목ë¡ì€ 공개ë©ë‹ˆë‹¤. 뮤트 목ë¡ì€ 공개ë˜ì§€ 않습니다." #: src/view/com/modals/SwitchAccount.tsx:84 -#: src/view/screens/Settings/index.tsx:118 +#: src/view/screens/Settings/index.tsx:122 msgid "Your profile" msgstr "ë‚´ 프로필" -#: src/view/com/composer/Composer.tsx:273 +#: src/view/com/composer/Composer.tsx:282 msgid "Your reply has been published" msgstr "ë‚´ ë‹µê¸€ì„ ê²Œì‹œí–ˆìŠµë‹ˆë‹¤" diff --git a/src/locale/locales/pt-BR/messages.po b/src/locale/locales/pt-BR/messages.po index 5cb145147..bfd794c55 100644 --- a/src/locale/locales/pt-BR/messages.po +++ b/src/locale/locales/pt-BR/messages.po @@ -8,8 +8,8 @@ msgstr "" "Language: pt-BR\n" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2024-02-08 19:59\n" -"Last-Translator: maisondasilva\n" +"PO-Revision-Date: 2024-03-12 11:36\n" +"Last-Translator: gildaswise\n" "Language-Team: maisondasilva, MightyLoggor, gildaswise, gleydson, faeriarum\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" @@ -17,28 +17,10 @@ msgstr "" msgid "(no email)" msgstr "(sem email)" -#: src/view/shell/desktop/RightNav.tsx:168 -#~ msgid "{0, plural, one {# invite code available} other {# invite codes available}}" -#~ msgstr "{0, plural, one {# convite disponÃvel} other {# convites disponÃveis}}" - #: src/view/com/profile/ProfileHeader.tsx:593 msgid "{following} following" msgstr "{following} seguindo" -#: src/view/shell/desktop/RightNav.tsx:151 -#~ msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" -#~ msgstr "{invitesAvailable, plural, one {Convites: # disponÃvel} other {Convites: # disponÃveis}}" - -#: src/view/screens/Settings.tsx:435 -#: src/view/shell/Drawer.tsx:664 -#~ msgid "{invitesAvailable} invite code available" -#~ msgstr "{invitesAvailable} convite disponÃvel" - -#: src/view/screens/Settings.tsx:437 -#: src/view/shell/Drawer.tsx:666 -#~ msgid "{invitesAvailable} invite codes available" -#~ msgstr "{invitesAvailable} convites disponÃveis" - #: src/view/shell/Drawer.tsx:440 msgid "{numUnreadNotifications} unread" msgstr "{numUnreadNotifications} não lidas" @@ -179,11 +161,11 @@ msgstr "Adicionar prévia de link:" #: src/components/dialogs/MutedWords.tsx:158 msgid "Add mute word for configured settings" -msgstr "" +msgstr "Adicionar palavra silenciada para as configurações selecionadas" #: src/components/dialogs/MutedWords.tsx:87 msgid "Add muted words and tags" -msgstr "" +msgstr "Adicionar palavras/tags silenciadas" #: src/view/com/modals/ChangeHandle.tsx:417 msgid "Add the following DNS record to your domain:" @@ -223,17 +205,13 @@ msgstr "Conteúdo Adulto" msgid "Adult content can only be enabled via the Web at <0/>." msgstr "Conteúdo adulto só pode ser habilitado no site: <0/>." -#: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:78 -#~ msgid "Adult content can only be enabled via the Web at <0>bsky.app</0>." -#~ msgstr "Conteúdo adulto só pode ser habilitado no site: <0>bsky.app</0>." - #: src/view/screens/Settings/index.tsx:664 msgid "Advanced" msgstr "Avançado" #: src/view/screens/Feeds.tsx:666 msgid "All the feeds you've saved, right in one place." -msgstr "" +msgstr "Todos os feeds que você salvou, em um único lugar." #: src/view/com/auth/login/ForgotPasswordForm.tsx:221 #: src/view/com/modals/ChangePassword.tsx:168 @@ -458,7 +436,7 @@ msgstr "Bluesky" #: src/view/com/auth/server-input/index.tsx:150 msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers." -msgstr "" +msgstr "Bluesky é uma rede aberta que permite a escolha do seu provedor de hospedagem. Desenvolvedores já conseguem utilizar a versão beta de hospedagem própria." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:80 @@ -483,10 +461,6 @@ msgstr "Bluesky é público." 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 "O Bluesky não mostrará seu perfil e publicações para usuários desconectados. Outros aplicativos podem não honrar esta solicitação. Isso não torna a sua conta privada." -#: src/view/com/modals/ServerInput.tsx:78 -#~ msgid "Bluesky.Social" -#~ msgstr "Bluesky.Social" - #: src/screens/Onboarding/index.tsx:33 msgid "Books" msgstr "Livros" @@ -500,10 +474,6 @@ msgstr "Versão {0} {1}" msgid "Business" msgstr "Empresarial" -#: src/view/com/modals/ServerInput.tsx:115 -#~ msgid "Button disabled. Input custom domain to proceed." -#~ msgstr "Botão desabilitado. Utilize um domÃnio personalizado para continuar." - #: src/view/com/profile/ProfileSubpageHeader.tsx:157 msgid "by —" msgstr "por -" @@ -666,10 +636,6 @@ msgid "Choose the algorithms that power your experience with custom feeds." msgstr "Escolha os algoritmos que fazem sentido para você com os feeds personalizados." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 -#~ msgid "Choose your algorithmic feeds" -#~ msgstr "Escolha seus feeds algoritmicos" - -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 msgid "Choose your main feeds" msgstr "Escolha seus feeds principais" @@ -706,11 +672,11 @@ msgstr "clique aqui" #: src/components/TagMenu/index.web.tsx:138 msgid "Click here to open tag menu for {tag}" -msgstr "" +msgstr "Clique aqui para abrir o menu da tag {tag}" #: src/components/RichText.tsx:191 msgid "Click here to open tag menu for #{tag}" -msgstr "" +msgstr "Clique aqui para abrir o menu da tag #{tag}" #: src/screens/Onboarding/index.tsx:35 msgid "Climate" @@ -748,7 +714,7 @@ msgstr "Fechar o painel de navegação" #: src/components/TagMenu/index.tsx:262 msgid "Close this dialog" -msgstr "" +msgstr "Fechar esta janela" #: src/view/shell/index.web.tsx:52 msgid "Closes bottom navigation bar" @@ -789,7 +755,7 @@ msgstr "Completar e começar a usar sua conta" #: src/view/com/auth/create/Step3.tsx:73 msgid "Complete the challenge" -msgstr "" +msgstr "Complete o captcha" #: src/view/com/composer/Composer.tsx:424 msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" @@ -964,10 +930,6 @@ msgstr "Não foi possÃvel carregar o feed" msgid "Could not load list" msgstr "Não foi possÃvel carregar a lista" -#: src/view/com/auth/create/Step2.tsx:91 -#~ msgid "Country" -#~ msgstr "PaÃs" - #: src/view/com/auth/HomeLoggedOutCTA.tsx:62 #: src/view/com/auth/SplashScreen.tsx:71 #: src/view/com/auth/SplashScreen.web.tsx:81 @@ -1014,7 +976,7 @@ msgstr "Cultura" #: src/view/com/auth/server-input/index.tsx:95 #: src/view/com/auth/server-input/index.tsx:96 msgid "Custom" -msgstr "" +msgstr "Customizado" #: src/view/com/modals/ChangeHandle.tsx:389 msgid "Custom domain" @@ -1029,10 +991,6 @@ msgstr "Feeds customizados feitos pela comunidade te proporcionam novas experiê msgid "Customize media from external sites." msgstr "Configurar mÃdia de sites externos." -#: src/view/screens/Settings.tsx:687 -#~ msgid "Danger Zone" -#~ msgstr "Zona Perigosa" - #: src/view/screens/Settings/index.tsx:485 #: src/view/screens/Settings/index.tsx:511 msgid "Dark" @@ -1072,10 +1030,6 @@ msgstr "Excluir Lista" msgid "Delete my account" msgstr "Excluir minha conta" -#: src/view/screens/Settings.tsx:706 -#~ msgid "Delete my account…" -#~ msgstr "Excluir minha conta…" - #: src/view/screens/Settings/index.tsx:784 msgid "Delete My Account…" msgstr "Excluir minha conta…" @@ -1133,13 +1087,9 @@ msgstr "Desencorajar aplicativos a mostrar minha conta para usuários deslogados msgid "Discover new custom feeds" msgstr "Descubra novos feeds" -#: src/view/screens/Feeds.tsx:473 -#~ msgid "Discover new feeds" -#~ msgstr "Descubra novos feeds" - #: src/view/screens/Feeds.tsx:689 msgid "Discover New Feeds" -msgstr "" +msgstr "Descubra Novos Feeds" #: src/view/com/modals/EditProfile.tsx:192 msgid "Display name" @@ -1196,12 +1146,12 @@ msgstr "Toque duas vezes para logar" #: src/view/screens/Settings/index.tsx:755 msgid "Download Bluesky account data (repository)" -msgstr "" +msgstr "Baixar os dados da minha conta Bluesky (repositório)" #: src/view/screens/Settings/ExportCarDialog.tsx:59 #: src/view/screens/Settings/ExportCarDialog.tsx:63 msgid "Download CAR file" -msgstr "" +msgstr "Baixar arquivo CAR" #: src/view/com/composer/text-input/TextInput.web.tsx:249 msgid "Drop to add images" @@ -1360,7 +1310,7 @@ msgstr "Insira um nome para esta Senha de Aplicativo" #: src/components/dialogs/MutedWords.tsx:100 #: src/components/dialogs/MutedWords.tsx:101 msgid "Enter a word or tag" -msgstr "" +msgstr "Digite uma palavra ou tag" #: src/view/com/modals/VerifyEmail.tsx:105 msgid "Enter Confirmation Code" @@ -1399,17 +1349,13 @@ msgstr "Digite o novo e-mail acima" msgid "Enter your new email address below." msgstr "Digite seu novo endereço de e-mail abaixo." -#: src/view/com/auth/create/Step2.tsx:188 -#~ msgid "Enter your phone number" -#~ msgstr "Digite seu número de telefone" - #: src/view/com/auth/login/Login.tsx:99 msgid "Enter your username and password" msgstr "Digite seu nome de usuário e senha" #: src/view/com/auth/create/Step3.tsx:67 msgid "Error receiving captcha response." -msgstr "" +msgstr "Não foi possÃvel processar o captcha." #: src/view/screens/Search/Search.tsx:110 msgid "Error:" @@ -1447,12 +1393,12 @@ msgstr "Mostrar ou esconder o post a que você está respondendo" #: src/view/screens/Settings/index.tsx:753 msgid "Export my data" -msgstr "" +msgstr "Exportar meus dados" #: src/view/screens/Settings/ExportCarDialog.tsx:44 #: src/view/screens/Settings/index.tsx:764 msgid "Export My Data" -msgstr "" +msgstr "Exportar Meus Dados" #: src/view/com/modals/EmbedConsent.tsx:64 msgid "External Media" @@ -1523,14 +1469,6 @@ msgstr "Comentários" msgid "Feeds" msgstr "Feeds" -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 -#~ msgid "Feeds are created by users and can give you entirely new experiences." -#~ msgstr "Feeds são criados por usuários e podem te dar experiências completamente únicas." - -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 -#~ msgid "Feeds are created by users and organizations. They offer you varied experiences and suggest content you may like using algorithms." -#~ msgstr "Feeds são criados por usuários ou organizações. Eles oferecem experiências únicas e podem te sugerir conteúdo usando algoritmos próprios." - #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:57 msgid "Feeds are created by users to curate content. Choose some feeds that you find interesting." msgstr "Os feeds são criados por usuários para curadoria de conteúdo. Escolha alguns feeds que você acha interessantes." @@ -1567,11 +1505,7 @@ msgstr "Procurando contas semelhantes..." #: src/view/screens/PreferencesFollowingFeed.tsx:111 msgid "Fine-tune the content you see on your Following feed." -msgstr "" - -#: src/view/screens/PreferencesHomeFeed.tsx:111 -#~ msgid "Fine-tune the content you see on your home screen." -#~ msgstr "Ajuste o conteúdo que você vê na sua tela inicial." +msgstr "Ajuste o conteúdo que você vê na sua tela inicial." #: src/view/screens/PreferencesThreads.tsx:60 msgid "Fine-tune the discussion threads." @@ -1659,7 +1593,7 @@ msgstr "Seguindo {0}" #: src/view/screens/PreferencesFollowingFeed.tsx:104 #: src/view/screens/Settings/index.tsx:543 msgid "Following Feed Preferences" -msgstr "" +msgstr "Configurações do feed principal" #: src/view/com/profile/ProfileHeader.tsx:546 msgid "Follows you" @@ -1697,7 +1631,7 @@ msgstr "Esqueci a Senha" #: src/screens/Hashtag.tsx:108 #: src/screens/Hashtag.tsx:148 msgid "From @{sanitizedAuthor}" -msgstr "" +msgstr "De @{sanitizedAuthor}" #: src/view/com/posts/FeedItem.tsx:189 msgctxt "from-feed" @@ -1751,15 +1685,15 @@ msgstr "Usuário" #: src/Navigation.tsx:270 msgid "Hashtag" -msgstr "" +msgstr "Hashtag" #: src/components/RichText.tsx:188 #~ msgid "Hashtag: {tag}" -#~ msgstr "" +#~ msgstr "Hashtag: {tag}" #: src/components/RichText.tsx:190 msgid "Hashtag: #{tag}" -msgstr "" +msgstr "Hashtag: #{tag}" #: src/view/com/auth/create/CreateAccount.tsx:208 msgid "Having trouble?" @@ -1930,10 +1864,6 @@ msgstr "Insira a nova senha" msgid "Input password for account deletion" msgstr "Insira a senha para excluir a conta" -#: src/view/com/auth/create/Step2.tsx:196 -#~ msgid "Input phone number for SMS verification" -#~ msgstr "Insira o número de telefone para verificação via SMS" - #: src/view/com/auth/login/LoginForm.tsx:230 msgid "Input the password tied to {identifier}" msgstr "Insira a senha da conta {identifier}" @@ -1942,10 +1872,6 @@ msgstr "Insira a senha da conta {identifier}" msgid "Input the username or email address you used at signup" msgstr "Insira o usuário ou e-mail que você cadastrou" -#: src/view/com/auth/create/Step2.tsx:271 -#~ msgid "Input the verification code we have texted to you" -#~ msgstr "Insira o código de verificação que enviamos para você" - #: src/view/com/modals/Waitlist.tsx:90 #~ msgid "Input your email to get on the Bluesky waitlist" #~ msgstr "Insira seu e-mail para entrar na lista de espera do Bluesky" @@ -1966,10 +1892,6 @@ msgstr "Post inválido" msgid "Invalid username or password" msgstr "Credenciais inválidas" -#: src/view/screens/Settings.tsx:411 -#~ msgid "Invite" -#~ msgstr "Convidar" - #: src/view/com/modals/InviteCodes.tsx:93 msgid "Invite a Friend" msgstr "Convide um Amigo" @@ -1987,10 +1909,6 @@ msgstr "Convite inválido. Verifique se você o inseriu corretamente e tente nov msgid "Invite codes: {0} available" msgstr "Convites: {0} disponÃveis" -#: src/view/shell/Drawer.tsx:645 -#~ msgid "Invite codes: {invitesAvailable} available" -#~ msgstr "Convites: {invitesAvailable} disponÃvel" - #: src/view/com/modals/InviteCodes.tsx:169 msgid "Invite codes: 1 available" msgstr "Convites: 1 disponÃvel" @@ -2232,15 +2150,15 @@ msgstr "Certifique-se de onde está indo!" #: src/components/dialogs/MutedWords.tsx:83 msgid "Manage your muted words and tags" -msgstr "" +msgstr "Gerencie suas palavras/tags silenciadas" #: src/view/com/auth/create/Step2.tsx:118 msgid "May not be longer than 253 characters" -msgstr "" +msgstr "Não pode ter mais que 253 caracteres" #: src/view/com/auth/create/Step2.tsx:109 msgid "May only contain letters and numbers" -msgstr "" +msgstr "Só pode conter letras e números" #: src/view/screens/Profile.tsx:182 msgid "Media" @@ -2332,15 +2250,15 @@ msgstr "Respostas mais curtidas primeiro" #: src/view/com/auth/create/Step2.tsx:122 msgid "Must be at least 3 characters" -msgstr "" +msgstr "Deve ter no mÃnimo 3 caracteres" #: src/components/TagMenu/index.tsx:249 msgid "Mute" -msgstr "" +msgstr "Silenciar" #: src/components/TagMenu/index.web.tsx:105 msgid "Mute {truncatedTag}" -msgstr "" +msgstr "Silenciar {truncatedTag}" #: src/view/com/profile/ProfileHeader.tsx:327 msgid "Mute Account" @@ -2352,19 +2270,19 @@ msgstr "Silenciar contas" #: src/components/TagMenu/index.tsx:209 msgid "Mute all {displayTag} posts" -msgstr "" +msgstr "Silenciar posts com {displayTag}" #: src/components/TagMenu/index.tsx:211 #~ msgid "Mute all {tag} posts" -#~ msgstr "" +#~ msgstr "Silenciar posts com {tag}" #: src/components/dialogs/MutedWords.tsx:149 msgid "Mute in tags only" -msgstr "" +msgstr "Silenciar apenas as tags" #: src/components/dialogs/MutedWords.tsx:134 msgid "Mute in text & tags" -msgstr "" +msgstr "Silenciar texto e tags" #: src/view/screens/ProfileList.tsx:491 msgid "Mute list" @@ -2380,11 +2298,11 @@ msgstr "Silenciar esta lista" #: src/components/dialogs/MutedWords.tsx:127 msgid "Mute this word in post text and tags" -msgstr "" +msgstr "Silenciar esta palavra no conteúdo de um post e tags" #: src/components/dialogs/MutedWords.tsx:142 msgid "Mute this word in tags only" -msgstr "" +msgstr "Silenciar esta palavra apenas nas tags de um post" #: src/view/com/util/forms/PostDropdownBtn.tsx:251 #: src/view/com/util/forms/PostDropdownBtn.tsx:257 @@ -2394,7 +2312,7 @@ msgstr "Silenciar thread" #: src/view/com/util/forms/PostDropdownBtn.tsx:267 #: src/view/com/util/forms/PostDropdownBtn.tsx:269 msgid "Mute words & tags" -msgstr "" +msgstr "Silenciar palavras/tags" #: src/view/com/lists/ListCard.tsx:102 msgid "Muted" @@ -2415,7 +2333,7 @@ msgstr "Contas silenciadas não aparecem no seu feed ou nas suas notificações. #: src/view/screens/Moderation.tsx:100 msgid "Muted words & tags" -msgstr "" +msgstr "Palavras/tags silenciadas" #: src/view/screens/ProfileList.tsx:277 msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." @@ -2439,7 +2357,7 @@ msgstr "Meus Feeds Salvos" #: src/view/com/auth/server-input/index.tsx:118 msgid "my-server.com" -msgstr "" +msgstr "meu-servidor.com.br" #: src/view/com/modals/AddAppPasswords.tsx:179 #: src/view/com/modals/CreateOrEditList.tsx:290 @@ -2482,7 +2400,7 @@ msgstr "Nunca perca o acesso aos seus seguidores ou dados." #: src/components/dialogs/MutedWords.tsx:293 msgid "Nevermind" -msgstr "" +msgstr "Deixa pra lá" #: src/view/screens/Lists.tsx:76 msgctxt "action" @@ -2587,7 +2505,7 @@ msgstr "Nenhum resultado" #: src/components/Lists.tsx:192 msgid "No results found" -msgstr "" +msgstr "Nenhum resultado encontrado" #: src/view/screens/Feeds.tsx:495 msgid "No results found for \"{query}\"" @@ -2669,7 +2587,7 @@ msgstr "Apenas {0} pode responder." #: src/components/Lists.tsx:82 msgid "Oops, something went wrong!" -msgstr "" +msgstr "Opa, algo deu errado!" #: src/components/Lists.tsx:188 #: src/view/screens/AppPasswords.tsx:65 @@ -2683,7 +2601,7 @@ msgstr "Abrir" #: src/view/screens/Moderation.tsx:75 msgid "Open content filtering settings" -msgstr "" +msgstr "Abrir configurações de filtro" #: src/view/com/composer/Composer.tsx:477 #: src/view/com/composer/Composer.tsx:478 @@ -2696,7 +2614,7 @@ msgstr "Abrir links no navegador interno" #: src/view/screens/Moderation.tsx:92 msgid "Open muted words settings" -msgstr "" +msgstr "Abrir configurações das palavras silenciadas" #: src/view/com/home/HomeHeaderLayoutMobile.tsx:50 msgid "Open navigation" @@ -2704,7 +2622,7 @@ msgstr "Abrir navegação" #: src/view/com/util/forms/PostDropdownBtn.tsx:175 msgid "Open post options menu" -msgstr "" +msgstr "Abrir opções do post" #: src/view/screens/Settings/index.tsx:804 msgid "Open storybook page" @@ -2754,10 +2672,6 @@ msgstr "Abre lista de seguidores" msgid "Opens following list" msgstr "Abre lista de seguidos" -#: src/view/screens/Settings.tsx:412 -#~ msgid "Opens invite code list" -#~ msgstr "Abre lista de convites" - #: src/view/com/modals/InviteCodes.tsx:172 msgid "Opens list of invite codes" msgstr "Abre a lista de códigos de convite" @@ -2819,10 +2733,6 @@ msgstr "Ou combine estas opções:" msgid "Other account" msgstr "Outra conta" -#: src/view/com/modals/ServerInput.tsx:88 -#~ msgid "Other service" -#~ msgstr "Outro serviço" - #: src/view/com/composer/select-language/SelectLangBtn.tsx:91 msgid "Other..." msgstr "Outro..." @@ -2872,10 +2782,6 @@ msgstr "A permissão de galeria foi recusada. Por favor, habilite-a nas configur msgid "Pets" msgstr "Pets" -#: src/view/com/auth/create/Step2.tsx:183 -#~ msgid "Phone number" -#~ msgstr "Número de telefone" - #: src/view/com/modals/SelfLabel.tsx:121 msgid "Pictures meant for adults." msgstr "Imagens destinadas a adultos." @@ -2912,7 +2818,7 @@ msgstr "Por favor, escolha sua senha." #: src/view/com/auth/create/state.ts:131 msgid "Please complete the verification captcha." -msgstr "" +msgstr "Por favor, complete o captcha de verificação." #: src/view/com/modals/ChangeEmail.tsx:67 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." @@ -2922,17 +2828,13 @@ msgstr "Por favor, confirme seu e-mail antes de alterá-lo. Este é um requisito msgid "Please enter a name for your app password. All spaces is not allowed." msgstr "Por favor, insira um nome para a sua Senha de Aplicativo." -#: src/view/com/auth/create/Step2.tsx:206 -#~ msgid "Please enter a phone number that can receive SMS text messages." -#~ msgstr "Por favor, insira um número de telefone que possa receber mensagens SMS." - #: src/view/com/modals/AddAppPasswords.tsx:145 msgid "Please enter a unique name for this App Password or use our randomly generated one." msgstr "Por favor, insira um nome único para esta Senha de Aplicativo ou use nosso nome gerado automaticamente." #: src/components/dialogs/MutedWords.tsx:68 msgid "Please enter a valid word, tag, or phrase to mute" -msgstr "" +msgstr "Por favor, insira uma palavra, tag ou frase para silenciar" #: src/view/com/auth/create/state.ts:170 #~ msgid "Please enter the code you received by SMS." @@ -2955,11 +2857,6 @@ msgstr "Por favor, digite sua senha também:" msgid "Please tell us why you think this content warning was incorrectly applied!" msgstr "Por favor, diga-nos por que você acha que este aviso de conteúdo foi aplicado incorretamente!" -#: src/view/com/modals/AppealLabel.tsx:72 -#: src/view/com/modals/AppealLabel.tsx:75 -#~ msgid "Please tell us why you think this decision was incorrect." -#~ msgstr "Por favor, conte-nos por que achou que esta decisão está incorreta." - #: src/view/com/modals/VerifyEmail.tsx:101 msgid "Please Verify Your Email" msgstr "Por favor, verifique seu e-mail" @@ -3019,7 +2916,7 @@ msgstr "Post não encontrado" #: src/components/TagMenu/index.tsx:253 msgid "posts" -msgstr "" +msgstr "posts" #: src/view/screens/Profile.tsx:180 msgid "Posts" @@ -3027,7 +2924,7 @@ msgstr "Posts" #: src/components/dialogs/MutedWords.tsx:90 msgid "Posts can be muted based on their text, their tags, or both." -msgstr "" +msgstr "Posts podem ser silenciados baseados no seu conteúdo, tags ou ambos." #: src/view/com/posts/FeedErrorMessage.tsx:64 msgid "Posts hidden" @@ -3171,7 +3068,7 @@ msgstr "Remover visualização da imagem" #: src/components/dialogs/MutedWords.tsx:343 msgid "Remove mute word from your list" -msgstr "" +msgstr "Remover palavra silenciada da lista" #: src/view/com/modals/Repost.tsx:47 msgid "Remove repost" @@ -3286,10 +3183,6 @@ msgstr "Reposts" msgid "Request Change" msgstr "Solicitar Alteração" -#: src/view/com/auth/create/Step2.tsx:219 -#~ msgid "Request code" -#~ msgstr "Solicitar código" - #: src/view/com/modals/ChangePassword.tsx:239 #: src/view/com/modals/ChangePassword.tsx:241 msgid "Request Code" @@ -3368,10 +3261,6 @@ msgstr "Tente novamente" msgid "Return to previous page" msgstr "Voltar para página anterior" -#: src/view/shell/desktop/RightNav.tsx:55 -#~ msgid "SANDBOX. Posts and accounts are not permanent." -#~ msgstr "SANDBOX. Posts e contas não são permanentes." - #: src/view/com/lightbox/Lightbox.tsx:132 #: src/view/com/modals/CreateOrEditList.tsx:345 msgctxt "action" @@ -3447,19 +3336,19 @@ msgstr "Pesquisar por \"{query}\"" #: src/components/TagMenu/index.tsx:145 msgid "Search for all posts by @{authorHandle} with tag {displayTag}" -msgstr "" +msgstr "Pesquisar por posts de @{authorHandle} com a tag {displayTag}" #: src/components/TagMenu/index.tsx:145 #~ msgid "Search for all posts by @{authorHandle} with tag {tag}" -#~ msgstr "" +#~ msgstr "Pesquisar por posts de @{authorHandle} com a tag {tag}" #: src/components/TagMenu/index.tsx:94 msgid "Search for all posts with tag {displayTag}" -msgstr "" +msgstr "Pesquisar por posts com a tag {displayTag}" #: src/components/TagMenu/index.tsx:90 #~ msgid "Search for all posts with tag {tag}" -#~ msgstr "" +#~ msgstr "Pesquisar por posts com a tag {tag}" #: src/view/com/auth/LoggedOut.tsx:104 #: src/view/com/auth/LoggedOut.tsx:105 @@ -3473,27 +3362,27 @@ msgstr "Passo de Segurança Necessário" #: src/components/TagMenu/index.web.tsx:66 msgid "See {truncatedTag} posts" -msgstr "" +msgstr "Ver posts com {truncatedTag}" #: src/components/TagMenu/index.web.tsx:83 msgid "See {truncatedTag} posts by user" -msgstr "" +msgstr "Ver posts com {truncatedTag} deste usuário" #: src/components/TagMenu/index.tsx:128 msgid "See <0>{displayTag}</0> posts" -msgstr "" +msgstr "Ver posts com <0>{displayTag}</0>" #: src/components/TagMenu/index.tsx:187 msgid "See <0>{displayTag}</0> posts by this user" -msgstr "" +msgstr "Ver posts com <0>{displayTag}</0> deste usuário" #: src/components/TagMenu/index.tsx:128 #~ msgid "See <0>{tag}</0> posts" -#~ msgstr "" +#~ msgstr "Ver posts com <0>{tag}</0>" #: src/components/TagMenu/index.tsx:189 #~ msgid "See <0>{tag}</0> posts by this user" -#~ msgstr "" +#~ msgstr "Ver posts com <0>{tag}</0> deste usuário" #: src/view/screens/SavedFeeds.tsx:163 msgid "See this guide" @@ -3507,10 +3396,6 @@ msgstr "Veja o que vem por aÃ" msgid "Select {item}" msgstr "Selecionar {item}" -#: src/view/com/modals/ServerInput.tsx:75 -#~ msgid "Select Bluesky Social" -#~ msgstr "Selecionar Bluesky Social" - #: src/view/com/auth/login/Login.tsx:117 msgid "Select from an existing account" msgstr "Selecionar de uma conta existente" @@ -3530,11 +3415,7 @@ msgstr "Selecione algumas contas para seguir" #: src/view/com/auth/server-input/index.tsx:82 msgid "Select the service that hosts your data." -msgstr "" - -#: src/screens/Onboarding/StepModeration/index.tsx:49 -#~ msgid "Select the types of content that you want to see (or not see), and we'll handle the rest." -#~ msgstr "Selecione os tipos de conteúdo que você quer (ou não) ver, e cuidaremos do resto." +msgstr "Selecione o serviço que hospeda seus dados." #: src/screens/Onboarding/StepTopicalFeeds.tsx:96 msgid "Select topical feeds to follow from the list below" @@ -3556,10 +3437,6 @@ msgstr "Selecione o idioma do seu aplicativo" msgid "Select your interests from the options below" msgstr "Selecione seus interesses" -#: src/view/com/auth/create/Step2.tsx:155 -#~ msgid "Select your phone's country" -#~ msgstr "Selecione o paÃs do número de telefone" - #: src/view/screens/LanguageSettings.tsx:190 msgid "Select your preferred language for translations in your feed." msgstr "Selecione seu idioma preferido para as traduções no seu feed." @@ -3601,7 +3478,7 @@ msgstr "Envia o e-mail com o código de confirmação para excluir a conta" #: src/view/com/auth/server-input/index.tsx:110 msgid "Server address" -msgstr "" +msgstr "URL do servidor" #: src/view/com/modals/ContentFilteringSettings.tsx:311 msgid "Set {value} for {labelGroup} content moderation policy" @@ -3657,13 +3534,9 @@ msgstr "Defina esta configuração como \"Não\" para ocultar todos os reposts d msgid "Set this setting to \"Yes\" to show replies in a threaded view. This is an experimental feature." msgstr "Defina esta configuração como \"Sim\" para mostrar respostas em uma visualização de thread. Este é um recurso experimental." -#: src/view/screens/PreferencesHomeFeed.tsx:261 -#~ msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your following feed. This is an experimental feature." -#~ msgstr "Defina esta configuração como \"Sim\" para mostrar amostras de seus feeds salvos na sua página inicial. Este é um recurso experimental." - #: src/view/screens/PreferencesFollowingFeed.tsx:261 msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your Following feed. This is an experimental feature." -msgstr "" +msgstr "Defina esta configuração como \"Sim\" para exibir amostras de seus feeds salvos no seu feed inicial. Este é um recurso experimental." #: src/screens/Onboarding/Layout.tsx:50 msgid "Set up your account" @@ -3893,10 +3766,6 @@ msgstr "Pular" msgid "Skip this flow" msgstr "Pular" -#: src/view/com/auth/create/Step2.tsx:82 -#~ msgid "SMS verification" -#~ msgstr "Verificação por SMS" - #: src/screens/Onboarding/index.tsx:40 msgid "Software Dev" msgstr "Desenvolvimento de software" @@ -3907,7 +3776,7 @@ msgstr "Desenvolvimento de software" #: src/components/Lists.tsx:203 msgid "Something went wrong!" -msgstr "" +msgstr "Algo deu errado!" #: src/view/com/modals/Waitlist.tsx:51 #~ msgid "Something went wrong. Check your email and try again." @@ -3933,10 +3802,6 @@ msgstr "Esportes" msgid "Square" msgstr "Quadrado" -#: src/view/com/modals/ServerInput.tsx:62 -#~ msgid "Staging" -#~ msgstr "Staging" - #: src/view/screens/Settings/index.tsx:871 msgid "Status page" msgstr "Página de status" @@ -3989,10 +3854,6 @@ msgstr "Sugestivo" msgid "Support" msgstr "Suporte" -#: src/view/com/modals/ProfilePreview.tsx:110 -#~ msgid "Swipe up to see more" -#~ msgstr "Deslize para cima para ver mais" - #: src/view/com/modals/SwitchAccount.tsx:117 msgid "Switch Account" msgstr "Alterar Conta" @@ -4017,15 +3878,15 @@ msgstr "Log do sistema" #: src/components/dialogs/MutedWords.tsx:337 msgid "tag" -msgstr "" +msgstr "tag" #: src/components/TagMenu/index.tsx:78 msgid "Tag menu: {displayTag}" -msgstr "" +msgstr "Menu da tag: {displayTag}" #: src/components/TagMenu/index.tsx:74 #~ msgid "Tag menu: {tag}" -#~ msgstr "" +#~ msgstr "Menu da tag: {tag}" #: src/view/com/modals/crop-image/CropImage.web.tsx:112 msgid "Tall" @@ -4052,7 +3913,7 @@ msgstr "Termos de Serviço" #: src/components/dialogs/MutedWords.tsx:337 msgid "text" -msgstr "" +msgstr "texto" #: src/view/com/modals/AppealLabel.tsx:70 #: src/view/com/modals/report/InputIssueDetails.tsx:51 @@ -4061,7 +3922,7 @@ msgstr "Campo de entrada de texto" #: src/view/com/auth/create/CreateAccount.tsx:94 msgid "That handle is already taken." -msgstr "" +msgstr "Este identificador de usuário já está sendo usado." #: src/view/com/profile/ProfileHeader.tsx:263 msgid "The account will be able to interact with you after unblocking." @@ -4179,10 +4040,6 @@ msgstr "Houve um problema inesperado no aplicativo. Por favor, deixe-nos saber s msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." msgstr "Muitos usuários estão tentando acessar o Bluesky! Ativaremos sua conta assim que possÃvel." -#: src/view/com/auth/create/Step2.tsx:55 -#~ msgid "There's something wrong with this number. Please choose your country and enter your full phone number!" -#~ msgstr "Houve um problema com este número. Por favor, escolha um paÃs e digite seu número de telefone completo!" - #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:138 msgid "These are popular accounts you might like:" msgstr "Estas são contas populares que talvez você goste:" @@ -4209,7 +4066,7 @@ msgstr "Este conteúdo não é visÃvel sem uma conta do Bluesky." #: src/view/screens/Settings/ExportCarDialog.tsx:75 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost.</0>" -msgstr "" +msgstr "Esta funcionalidade está em beta. Você pode ler mais sobre exportação de repositórios <0>neste post</0> do nosso blog." #: src/view/com/posts/FeedErrorMessage.tsx:114 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." @@ -4261,17 +4118,13 @@ msgstr "Este usuário está incluÃdo na lista <0/>, que você bloqueou." msgid "This user is included in the <0/> list which you have muted." msgstr "Este usuário está incluÃdo na lista <0/>, que você silenciou." -#: src/view/com/modals/ModerationDetails.tsx:74 -#~ msgid "This user is included the <0/> list which you have muted." -#~ msgstr "Este usuário está incluÃdo na lista <0/>, que você silenciou." - #: src/view/com/modals/SelfLabel.tsx:137 msgid "This warning is only available for posts with media attached." msgstr "Este aviso só está disponÃvel para publicações com mÃdia anexada." #: src/components/dialogs/MutedWords.tsx:285 msgid "This will delete {0} from your muted words. You can always add it back later." -msgstr "" +msgstr "Isso removerá {0} das suas palavras silenciadas. Você pode adicioná-la novamente depois." #: src/view/com/util/forms/PostDropdownBtn.tsx:282 msgid "This will hide this post from your feeds." @@ -4292,7 +4145,7 @@ msgstr "Preferências das Threads" #: src/components/dialogs/MutedWords.tsx:113 msgid "Toggle between muted word options." -msgstr "" +msgstr "Alternar entre opções de uma palavra silenciada" #: src/view/com/util/forms/DropdownButton.tsx:246 msgid "Toggle dropdown" @@ -4376,7 +4229,7 @@ msgstr "Dessilenciar" #: src/components/TagMenu/index.web.tsx:104 msgid "Unmute {truncatedTag}" -msgstr "" +msgstr "Dessilenciar {truncatedTag}" #: src/view/com/profile/ProfileHeader.tsx:326 msgid "Unmute Account" @@ -4384,11 +4237,11 @@ msgstr "Dessilenciar conta" #: src/components/TagMenu/index.tsx:208 msgid "Unmute all {displayTag} posts" -msgstr "" +msgstr "Dessilenciar posts com {displayTag}" #: src/components/TagMenu/index.tsx:210 #~ msgid "Unmute all {tag} posts" -#~ msgstr "" +#~ msgstr "Dessilenciar posts com {tag}" #: src/view/com/util/forms/PostDropdownBtn.tsx:251 #: src/view/com/util/forms/PostDropdownBtn.tsx:256 @@ -4446,10 +4299,6 @@ msgstr "Usar o meu navegador padrão" msgid "Use this to sign into the other app along with your handle." msgstr "Use esta senha para entrar no outro aplicativo juntamente com seu identificador." -#: src/view/com/modals/ServerInput.tsx:105 -#~ msgid "Use your domain as your Bluesky client service provider" -#~ msgstr "Use seu domÃnio como o provedor de serviço do Bluesky" - #: src/view/com/modals/InviteCodes.tsx:200 msgid "Used by:" msgstr "Usado por:" @@ -4514,10 +4363,6 @@ msgstr "usuários seguidos por <0/>" msgid "Users in \"{0}\"" msgstr "Usuários em \"{0}\"" -#: src/view/com/auth/create/Step2.tsx:243 -#~ msgid "Verification code" -#~ msgstr "Código de verificação" - #: src/view/screens/Settings/index.tsx:910 msgid "Verify email" msgstr "Verificar e-mail" @@ -4578,7 +4423,7 @@ msgstr "Também recomendamos o \"For You\", do Skygaze:" #: src/screens/Hashtag.tsx:132 msgid "We couldn't find any results for that hashtag." -msgstr "" +msgstr "Não encontramos nenhum post com esta hashtag." #: src/screens/Deactivated.tsx:133 msgid "We estimate {estimatedTime} until your account is ready." @@ -4598,7 +4443,7 @@ msgstr "Não temos mais posts de quem você segue. Aqui estão os mais novos de #: src/components/dialogs/MutedWords.tsx:204 msgid "We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." -msgstr "" +msgstr "Não recomendamos utilizar palavras comuns que aparecem em muitos posts, já que isso pode resultar em filtrar todos eles." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124 msgid "We recommend our \"Discover\" feed:" @@ -4630,7 +4475,7 @@ msgstr "Tivemos um problema ao exibir esta lista. Se continuar acontecendo, cont #: src/components/dialogs/MutedWords.tsx:230 msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." -msgstr "" +msgstr "Não foi possÃvel carregar sua lista de palavras silenciadas. Por favor, tente novamente." #: src/view/screens/Search/Search.tsx:254 msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." @@ -4688,10 +4533,6 @@ msgstr "Escreva sua resposta" msgid "Writers" msgstr "Escritores" -#: src/view/com/auth/create/Step2.tsx:263 -#~ msgid "XXXXXX" -#~ msgstr "XXXXXX" - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:77 #: src/view/screens/PreferencesFollowingFeed.tsx:129 #: src/view/screens/PreferencesFollowingFeed.tsx:201 @@ -4702,10 +4543,6 @@ msgstr "Escritores" msgid "Yes" msgstr "Sim" -#: src/screens/Onboarding/StepModeration/index.tsx:46 -#~ msgid "You are in control" -#~ msgstr "Você está no controle" - #: src/screens/Deactivated.tsx:130 msgid "You are in line." msgstr "Você está na fila." @@ -4715,10 +4552,6 @@ msgstr "Você está na fila." msgid "You can also discover new Custom Feeds to follow." msgstr "Você também pode descobrir novos feeds para seguir." -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:123 -#~ msgid "You can also try our \"Discover\" algorithm:" -#~ msgstr "Você também pode tentar nosso algoritmo \"Discover\":" - #: src/screens/Onboarding/StepFollowingFeed.tsx:142 msgid "You can change these settings later." msgstr "Você pode mudar estas configurações depois." @@ -4786,7 +4619,7 @@ msgstr "Você ainda não silenciou nenhuma conta. Para silenciar uma conta, aces #: src/components/dialogs/MutedWords.tsx:250 msgid "You haven't muted any words or tags yet" -msgstr "" +msgstr "Você não silenciou nenhuma palavra ou tag ainda" #: src/view/com/modals/ContentFilteringSettings.tsx:175 msgid "You must be 18 or older to enable adult content." @@ -4836,7 +4669,7 @@ msgstr "Sua conta foi excluÃda" #: src/view/screens/Settings/ExportCarDialog.tsx:47 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 "" +msgstr "O repositório da sua conta, contendo todos os seus dados públicos, pode ser baixado como um arquivo \"CAR\". Este arquivo não inclui imagens ou dados privados, estes devem ser exportados separadamente." #: src/view/com/auth/create/Step1.tsx:215 msgid "Your birth date" @@ -4888,7 +4721,7 @@ msgstr "Seu usuário completo será <0>@{0}</0>" #: src/components/dialogs/MutedWords.tsx:221 msgid "Your muted words" -msgstr "" +msgstr "Suas palavras silenciadas" #: src/view/com/modals/ChangePassword.tsx:155 msgid "Your password has been changed successfully!" diff --git a/src/locale/locales/uk/messages.po b/src/locale/locales/uk/messages.po index 41782f776..2af731a4b 100644 --- a/src/locale/locales/uk/messages.po +++ b/src/locale/locales/uk/messages.po @@ -8,7 +8,7 @@ msgstr "" "Language: uk\n" "Project-Id-Version: bsky-app-ua\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2024-01-09 23:26\n" +"PO-Revision-Date: 2024-03-13 11:56\n" "Last-Translator: \n" "Language-Team: Ukrainian\n" "Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" @@ -18,60 +18,35 @@ msgstr "" "X-Crowdin-File: /main/src/locale/locales/en/messages.po\n" "X-Crowdin-File-ID: 14\n" -#: src/view/screens/Profile.tsx:214 -#~ msgid "- end of feed -" -#~ msgstr "" - -#: src/view/com/modals/SelfLabel.tsx:138 -#~ msgid ". This warning is only available for posts with media attached." -#~ msgstr "" - #: src/view/com/modals/VerifyEmail.tsx:142 msgid "(no email)" -msgstr "" +msgstr "(немає ел. адреÑи)" #: src/view/shell/desktop/RightNav.tsx:168 #~ msgid "{0, plural, one {# invite code available} other {# invite codes available}}" -#~ msgstr "{0, plural, one {ДоÑтупний # код запрошеннÑ} few {ДоÑтупно # коди запрошеннÑ} other {ДоÑтупно # кодів запрошеннÑ}}" - -#: src/view/com/modals/CreateOrEditList.tsx:185 -#: src/view/screens/Settings.tsx:294 -#~ msgid "{0}" -#~ msgstr "{0}" - -#: src/view/com/modals/CreateOrEditList.tsx:176 -#~ msgid "{0} {purposeLabel} List" -#~ msgstr "{0} СпиÑок {purposeLabel}" +#~ msgstr "" #: src/view/com/profile/ProfileHeader.tsx:593 msgid "{following} following" -msgstr "" +msgstr "{following} підпиÑок" #: src/view/shell/desktop/RightNav.tsx:151 #~ msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" -#~ msgstr "{invitesAvailable, plural, one {Коди запрошень: доÑтупно #} other {Коди запрошень: доÑтупно #}}" +#~ msgstr "" #: src/view/screens/Settings.tsx:435 #: src/view/shell/Drawer.tsx:664 #~ msgid "{invitesAvailable} invite code available" -#~ msgstr "ДоÑтупний код запрошеннÑ" +#~ msgstr "" #: src/view/screens/Settings.tsx:437 #: src/view/shell/Drawer.tsx:666 #~ msgid "{invitesAvailable} invite codes available" -#~ msgstr "ДоÑтупно {invitesAvailable} кодів запрошеннÑ" - -#: src/view/screens/Search/Search.tsx:87 -#~ msgid "{message}" -#~ msgstr "{message}" +#~ msgstr "" #: src/view/shell/Drawer.tsx:440 msgid "{numUnreadNotifications} unread" -msgstr "" - -#: src/Navigation.tsx:147 -#~ msgid "@{0}" -#~ msgstr "" +msgstr "{numUnreadNotifications} непрочитаних" #: src/view/com/threadgate/WhoCanReply.tsx:158 msgid "<0/> members" @@ -79,7 +54,7 @@ msgstr "<0/> учаÑників" #: src/view/com/profile/ProfileHeader.tsx:595 msgid "<0>{following} </0><1>following</1>" -msgstr "" +msgstr "<0>{following} </0><1>підпиÑок</1>" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:30 msgid "<0>Choose your</0><1>Recommended</1><2>Feeds</2>" @@ -89,25 +64,13 @@ msgstr "<0>Оберіть Ñвої</0><1>рекомендовані</1><2>Ñтр msgid "<0>Follow some</0><1>Recommended</1><2>Users</2>" msgstr "<0>ПідпишітьÑÑ Ð½Ð° деÑких </0><1>рекомендованих </1><2>кориÑтувачів</2>" -#: src/view/com/modals/AddAppPasswords.tsx:132 -#~ msgid "<0>Here is your app password.</0> Use this to sign into the other app along with your handle." -#~ msgstr "" - -#: src/view/screens/Moderation.tsx:212 -#~ msgid "<0>Note: This setting may not be respected by third-party apps that display Bluesky content.</0>" -#~ msgstr "" - -#: src/view/screens/Moderation.tsx:212 -#~ msgid "<0>Note: Your profile and posts will remain publicly available. Third-party apps that display Bluesky content may not respect this setting.</0>" -#~ msgstr "" - #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:21 msgid "<0>Welcome to</0><1>Bluesky</1>" -msgstr "" +msgstr "<0>ЛаÑкаво проÑимо до</0><1>Bluesky</1>" #: src/view/com/profile/ProfileHeader.tsx:558 msgid "âš Invalid Handle" -msgstr "" +msgstr "âš ÐедопуÑтимий пÑевдонім" #: src/view/com/util/moderation/LabelInfo.tsx:45 msgid "A content warning has been applied to this {0}." @@ -120,11 +83,11 @@ msgstr "ДоÑтупна нова верÑÑ–Ñ. Будь лаÑка, Ð¾Ð½Ð¾Ð²Ñ–Ñ #: src/view/com/util/ViewHeader.tsx:89 #: src/view/screens/Search/Search.tsx:647 msgid "Access navigation links and settings" -msgstr "" +msgstr "Відкрити навігацію й налаштуваннÑ" #: src/view/com/home/HomeHeaderLayoutMobile.tsx:52 msgid "Access profile and other navigation links" -msgstr "" +msgstr "Відкрити профіль та іншу навігацію" #: src/view/com/modals/EditImage.tsx:299 #: src/view/screens/Settings/index.tsx:451 @@ -139,19 +102,19 @@ msgstr "Обліковий запиÑ" #: src/view/com/profile/ProfileHeader.tsx:246 msgid "Account blocked" -msgstr "" +msgstr "Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾" #: src/view/com/profile/ProfileHeader.tsx:213 msgid "Account muted" -msgstr "" +msgstr "Обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ–Ð³Ð½Ð¾Ñ€ÑƒÑ”Ñ‚ÑŒÑÑ" #: src/view/com/modals/ModerationDetails.tsx:86 msgid "Account Muted" -msgstr "" +msgstr "Обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ–Ð³Ð½Ð¾Ñ€ÑƒÑ”Ñ‚ÑŒÑÑ" #: src/view/com/modals/ModerationDetails.tsx:72 msgid "Account Muted by List" -msgstr "" +msgstr "Обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ–Ð³Ð½Ð¾Ñ€ÑƒÑ”Ñ‚ÑŒÑÑ ÑпиÑком" #: src/view/com/util/AccountDropdownBtn.tsx:41 msgid "Account options" @@ -159,15 +122,15 @@ msgstr "Параметри облікового запиÑу" #: src/view/com/util/AccountDropdownBtn.tsx:25 msgid "Account removed from quick access" -msgstr "" +msgstr "Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð¾ зі швидкого доÑтупу" #: src/view/com/profile/ProfileHeader.tsx:268 msgid "Account unblocked" -msgstr "" +msgstr "Обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ€Ð¾Ð·Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾" #: src/view/com/profile/ProfileHeader.tsx:226 msgid "Account unmuted" -msgstr "" +msgstr "Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð±Ñ–Ð»ÑŒÑˆÐµ не ігноруєтьÑÑ" #: src/components/dialogs/MutedWords.tsx:165 #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:150 @@ -200,7 +163,7 @@ msgstr "Додати альтернативний текÑÑ‚" #: src/view/screens/AppPasswords.tsx:143 #: src/view/screens/AppPasswords.tsx:156 msgid "Add App Password" -msgstr "" +msgstr "Додати пароль заÑтоÑунку" #: src/view/com/modals/report/InputIssueDetails.tsx:41 #: src/view/com/modals/report/Modal.tsx:191 @@ -221,11 +184,11 @@ msgstr "Додати попередній переглÑд:" #: src/components/dialogs/MutedWords.tsx:158 msgid "Add mute word for configured settings" -msgstr "" +msgstr "Додати Ñлово до Ñ–Ð³Ð½Ð¾Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð· обраними налаштуваннÑми" #: src/components/dialogs/MutedWords.tsx:87 msgid "Add muted words and tags" -msgstr "" +msgstr "Додати ігноровані Ñлова та теги" #: src/view/com/modals/ChangeHandle.tsx:417 msgid "Add the following DNS record to your domain:" @@ -242,7 +205,7 @@ msgstr "Додати до моїх Ñтрічок" #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:139 msgid "Added" -msgstr "" +msgstr "Додано" #: src/view/com/modals/ListAddRemoveUsers.tsx:191 #: src/view/com/modals/UserAddRemoveLists.tsx:144 @@ -251,7 +214,7 @@ msgstr "Додано до ÑпиÑку" #: src/view/com/feeds/FeedSourceCard.tsx:127 msgid "Added to my feeds" -msgstr "" +msgstr "Додано до моїх Ñтрічок" #: src/view/screens/PreferencesFollowingFeed.tsx:173 msgid "Adjust the number of likes a reply must have to be shown in your feed." @@ -263,7 +226,7 @@ msgstr "ВміÑÑ‚ Ð´Ð»Ñ Ð´Ð¾Ñ€Ð¾Ñлих" #: src/view/com/modals/ContentFilteringSettings.tsx:141 msgid "Adult content can only be enabled via the Web at <0/>." -msgstr "" +msgstr "ВміÑÑ‚ Ð´Ð»Ñ Ð´Ð¾Ñ€Ð¾Ñлих можна увімкнути лише у вебверÑÑ–Ñ— на <0/>." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:78 #~ msgid "Adult content can only be enabled via the Web at <0>bsky.app</0>." @@ -275,16 +238,16 @@ msgstr "Розширені" #: src/view/screens/Feeds.tsx:666 msgid "All the feeds you've saved, right in one place." -msgstr "" +msgstr "УÑÑ– збережені Ñтрічки в одному міÑці." #: src/view/com/auth/login/ForgotPasswordForm.tsx:221 #: src/view/com/modals/ChangePassword.tsx:168 msgid "Already have a code?" -msgstr "" +msgstr "Вже маєте код?" #: src/view/com/auth/login/ChooseAccountForm.tsx:98 msgid "Already signed in as @{0}" -msgstr "" +msgstr "Вже увійшли Ñк @{0}" #: src/view/com/composer/photos/Gallery.tsx:130 msgid "ALT" @@ -309,7 +272,7 @@ msgstr "Було надіÑлано лиÑÑ‚ на вашу попередню а #: src/view/com/profile/FollowButton.tsx:30 #: src/view/com/profile/FollowButton.tsx:40 msgid "An issue occurred, please try again." -msgstr "" +msgstr "Виникла проблема, будь лаÑка, Ñпробуйте ще раз." #: src/view/com/notifications/FeedItem.tsx:237 #: src/view/com/threadgate/WhoCanReply.tsx:178 @@ -318,7 +281,7 @@ msgstr "та" #: src/screens/Onboarding/index.tsx:32 msgid "Animals" -msgstr "" +msgstr "Тварини" #: src/view/screens/LanguageSettings.tsx:95 msgid "App Language" @@ -326,23 +289,23 @@ msgstr "Мова заÑтоÑунку" #: src/view/screens/AppPasswords.tsx:228 msgid "App password deleted" -msgstr "" +msgstr "Пароль заÑтоÑунку видалено" #: src/view/com/modals/AddAppPasswords.tsx:134 msgid "App Password names can only contain letters, numbers, spaces, dashes, and underscores." -msgstr "" +msgstr "Ðазва Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð¼Ð¾Ð¶Ðµ міÑтити лише латинÑькі літери, цифри, пробіли, мінуÑи та нижні підкреÑленнÑ." #: src/view/com/modals/AddAppPasswords.tsx:99 msgid "App Password names must be at least 4 characters long." -msgstr "" +msgstr "Ðазва Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð·Ð°ÑтоÑунку муÑить бути хоча б 4 Ñимволи в довжину." #: src/view/screens/Settings/index.tsx:675 msgid "App password settings" -msgstr "" +msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð·Ð°ÑтоÑунків" #: src/view/screens/Settings.tsx:650 #~ msgid "App passwords" -#~ msgstr "Паролі Ð´Ð»Ñ Ð·Ð°ÑтоÑунків" +#~ msgstr "" #: src/Navigation.tsx:239 #: src/view/screens/AppPasswords.tsx:187 @@ -359,10 +322,6 @@ msgstr "ОÑкаржити Ð¿Ð¾Ð¿ÐµÑ€ÐµÐ´Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ вміÑÑ‚" msgid "Appeal Content Warning" msgstr "ОÑкаржити Ð¿Ð¾Ð¿ÐµÑ€ÐµÐ´Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ вміÑÑ‚" -#: src/view/com/modals/AppealLabel.tsx:65 -#~ msgid "Appeal Decision" -#~ msgstr "" - #: src/view/com/util/moderation/LabelInfo.tsx:52 msgid "Appeal this decision" msgstr "ОÑкаржити це рішеннÑ" @@ -375,10 +334,6 @@ msgstr "ОÑкаржити це рішеннÑ" msgid "Appearance" msgstr "ОформленнÑ" -#: src/view/screens/Moderation.tsx:206 -#~ msgid "Apps that respect this setting, including the official Bluesky app and bsky.app website, won't show your content to logged out users." -#~ msgstr "" - #: src/view/screens/AppPasswords.tsx:224 msgid "Are you sure you want to delete the app password \"{name}\"?" msgstr "Ви дійÑно хочете видалити пароль Ð´Ð»Ñ Ð·Ð°ÑтоÑунку \"{name}\"?" @@ -398,20 +353,16 @@ msgstr "Ви впевнені? Це не можна буде ÑкаÑувати. #: src/view/com/composer/select-language/SuggestedLanguage.tsx:60 msgid "Are you writing in <0>{0}</0>?" -msgstr "" +msgstr "Ви пишете <0>{0}</0>?" #: src/screens/Onboarding/index.tsx:26 msgid "Art" -msgstr "" +msgstr "МиÑтецтво" #: src/view/com/modals/SelfLabel.tsx:123 msgid "Artistic or non-erotic nudity." msgstr "Ð¥ÑƒÐ´Ð¾Ð¶Ð½Ñ Ð°Ð±Ð¾ нееротична оголеніÑть." -#: src/view/screens/Moderation.tsx:189 -#~ msgid "Ask apps to limit the visibility of my account" -#~ msgstr "" - #: src/view/com/auth/create/CreateAccount.tsx:158 #: src/view/com/auth/login/ChooseAccountForm.tsx:151 #: src/view/com/auth/login/ForgotPasswordForm.tsx:174 @@ -429,11 +380,11 @@ msgstr "Ðазад" #: src/view/com/post-thread/PostThread.tsx:480 msgctxt "action" msgid "Back" -msgstr "" +msgstr "Ðазад" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:136 msgid "Based on your interest in {interestsText}" -msgstr "" +msgstr "ÒрунтуючиÑÑŒ на вашому інтереÑÑ– до {interestsText}" #: src/view/screens/Settings/index.tsx:523 msgid "Basics" @@ -467,12 +418,12 @@ msgstr "Заблокувати ці облікові запиÑи?" #: src/view/screens/ProfileList.tsx:320 msgid "Block this List" -msgstr "" +msgstr "Заблокувати ÑпиÑок" #: src/view/com/lists/ListCard.tsx:110 #: src/view/com/util/post-embeds/QuoteEmbed.tsx:61 msgid "Blocked" -msgstr "" +msgstr "Заблоковано" #: src/view/screens/Moderation.tsx:142 msgid "Blocked accounts" @@ -512,7 +463,7 @@ msgstr "Bluesky" #: src/view/com/auth/server-input/index.tsx:150 msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers." -msgstr "" +msgstr "Bluesky Ñ” відкритою мережею, де ви можете обрати Ñвого хоÑтинг-провайдера. ВлаÑний хоÑтинг тепер доÑтупний в бета-верÑÑ–Ñ— Ð´Ð»Ñ Ñ€Ð¾Ð·Ñ€Ð¾Ð±Ð½Ð¸ÐºÑ–Ð²." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:80 @@ -531,7 +482,7 @@ msgstr "Bluesky публічний." #: src/view/com/modals/Waitlist.tsx:70 #~ msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon." -#~ msgstr "Bluesky викориÑтовує ÑиÑтему запрошень Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð´Ð¾Ñ€Ð¾Ð²Ñ–ÑˆÐ¾Ñ— Ñпільноти. Якщо Ви не знаєте когоÑÑŒ хто має запрошеннÑ, ви можете запиÑатиÑÑ Ð´Ð¾ черги Ð¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ñ– ми Ñкоро надішлемо вам код запрошеннÑ." +#~ msgstr "" #: src/view/screens/Moderation.tsx:245 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." @@ -539,11 +490,11 @@ msgstr "Bluesky не буде показувати ваш профіль Ñ– по #: src/view/com/modals/ServerInput.tsx:78 #~ msgid "Bluesky.Social" -#~ msgstr "Bluesky.Social" +#~ msgstr "" #: src/screens/Onboarding/index.tsx:33 msgid "Books" -msgstr "" +msgstr "Книги" #: src/view/screens/Settings/index.tsx:859 msgid "Build version {0} {1}" @@ -560,19 +511,19 @@ msgstr "ОрганізаціÑ" #: src/view/com/profile/ProfileSubpageHeader.tsx:157 msgid "by —" -msgstr "" +msgstr "від —" #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:100 msgid "by {0}" -msgstr "" +msgstr "від {0}" #: src/view/com/profile/ProfileSubpageHeader.tsx:161 msgid "by <0/>" -msgstr "" +msgstr "від <0/>" #: src/view/com/profile/ProfileSubpageHeader.tsx:159 msgid "by you" -msgstr "" +msgstr "Ñтворено вами" #: src/view/com/composer/photos/OpenCameraBtn.tsx:60 #: src/view/com/util/UserAvatar.tsx:224 @@ -611,20 +562,16 @@ msgstr "СкаÑувати" #: src/view/com/modals/DeleteAccount.tsx:234 msgctxt "action" msgid "Cancel" -msgstr "" +msgstr "СкаÑувати" #: src/view/com/modals/DeleteAccount.tsx:152 #: src/view/com/modals/DeleteAccount.tsx:230 msgid "Cancel account deletion" msgstr "СкаÑувати Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу" -#: src/view/com/modals/AltImage.tsx:123 -#~ msgid "Cancel add image alt text" -#~ msgstr "" - #: src/view/com/modals/ChangeHandle.tsx:149 msgid "Cancel change handle" -msgstr "СкаÑувати зміну пÑевдоніму" +msgstr "СкаÑувати зміну пÑевдоніма" #: src/view/com/modals/crop-image/CropImage.web.tsx:134 msgid "Cancel image crop" @@ -632,11 +579,11 @@ msgstr "СкаÑувати Ð¾Ð±Ñ€Ñ–Ð·Ð°Ð½Ð½Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ" #: src/view/com/modals/EditProfile.tsx:244 msgid "Cancel profile editing" -msgstr "СкаÑувати Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ñ„Ñ–Ð»ÑŽ" +msgstr "СкаÑувати зміни профілю" #: src/view/com/modals/Repost.tsx:78 msgid "Cancel quote post" -msgstr "СкаÑувати Ñ†Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ" +msgstr "СкаÑувати Ñ†Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ñту" #: src/view/com/modals/ListAddRemoveUsers.tsx:87 #: src/view/shell/desktop/Search.tsx:234 @@ -645,16 +592,12 @@ msgstr "СкаÑувати пошук" #: src/view/com/modals/Waitlist.tsx:136 #~ msgid "Cancel waitlist signup" -#~ msgstr "СкаÑувати Ð·Ð°Ð¿Ð¸Ñ Ñƒ чергу очікуваннÑ" +#~ msgstr "" #: src/view/screens/Settings/index.tsx:334 msgctxt "action" msgid "Change" -msgstr "" - -#: src/view/screens/Settings.tsx:306 -#~ msgid "Change" -#~ msgstr "Змінити" +msgstr "Змінити" #: src/view/screens/Settings/index.tsx:696 msgid "Change handle" @@ -671,19 +614,19 @@ msgstr "Змінити адреÑу електронної пошти" #: src/view/screens/Settings/index.tsx:732 msgid "Change password" -msgstr "" +msgstr "Змінити пароль" #: src/view/screens/Settings/index.tsx:741 msgid "Change Password" -msgstr "" +msgstr "Зміна паролÑ" #: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 msgid "Change post language to {0}" -msgstr "" +msgstr "Змінити мову поÑта на {0}" #: src/view/screens/Settings/index.tsx:733 msgid "Change your Bluesky password" -msgstr "" +msgstr "Змінити ваш пароль Bluesky" #: src/view/com/modals/ChangeEmail.tsx:109 msgid "Change Your Email" @@ -692,7 +635,7 @@ msgstr "Змінити адреÑу електронної пошти" #: src/screens/Deactivated.tsx:72 #: src/screens/Deactivated.tsx:76 msgid "Check my status" -msgstr "" +msgstr "Перевірити мій ÑтатуÑ" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:121 msgid "Check out some recommended feeds. Tap + to add them to your list of pinned feeds." @@ -700,11 +643,11 @@ msgstr "ПодивітьÑÑ Ð½Ð° деÑкі з рекомендованих ÑÑ #: src/view/com/auth/onboarding/RecommendedFollows.tsx:185 msgid "Check out some recommended users. Follow them to see similar users." -msgstr "ПодивітьÑÑ Ð½Ð° деÑких рекомендованих кориÑтувачів. ПідпишітьÑÑ Ð½Ð° них щоб бачити Ñхожих кориÑтувачів." +msgstr "ОзнайомтеÑÑ Ð· деÑкими рекомендованими кориÑтувачами. Слідкуйте за ними, щоб побачити допиÑи від подібних кориÑтувачів." #: src/view/com/modals/DeleteAccount.tsx:169 msgid "Check your inbox for an email with the confirmation code to enter below:" -msgstr "Пошукайте у вашій поштовій Ñкриньці лиÑÑ‚ із кодом, щоб ввеÑти нижче:" +msgstr "Перевірте Ñвою поштову Ñкриньку на наÑвніÑть електронного лиÑта з кодом Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ‚Ð° введіть його нижче:" #: src/view/com/modals/Threadgate.tsx:72 msgid "Choose \"Everybody\" or \"Nobody\"" @@ -712,7 +655,7 @@ msgstr "Виберіть \"УÑÑ–\" або \"Ðіхто\"" #: src/view/screens/Settings/index.tsx:697 msgid "Choose a new Bluesky username or create" -msgstr "" +msgstr "Оберіть або Ñтворіть Ñвоє ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача" #: src/view/com/auth/server-input/index.tsx:79 msgid "Choose Service" @@ -720,24 +663,20 @@ msgstr "Оберіть хоÑтинг-провайдера" #: src/screens/Onboarding/StepFinished.tsx:135 msgid "Choose the algorithms that power your custom feeds." -msgstr "" +msgstr "Оберіть алгоритми, що наповнюватимуть ваші Ñтрічки." #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:83 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:83 msgid "Choose the algorithms that power your experience with custom feeds." msgstr "Ðвтори Ñтрічок можуть обирати будь-Ñкі алгоритми Ð´Ð»Ñ Ñ„Ð¾Ñ€Ð¼ÑƒÐ²Ð°Ð½Ð½Ñ Ñтрічки Ñаме Ð´Ð»Ñ Ð²Ð°Ñ." -#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:65 -#~ msgid "Choose your" -#~ msgstr "" - #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 #~ msgid "Choose your algorithmic feeds" #~ msgstr "" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 msgid "Choose your main feeds" -msgstr "" +msgstr "Виберіть ваші оÑновні Ñтрічки" #: src/view/com/auth/create/Step1.tsx:196 msgid "Choose your password" @@ -768,29 +707,29 @@ msgstr "ОчиÑтити пошуковий запит" #: src/view/screens/Support.tsx:40 msgid "click here" -msgstr "" +msgstr "натиÑніть тут" #: src/components/TagMenu/index.web.tsx:138 msgid "Click here to open tag menu for {tag}" -msgstr "" +msgstr "ÐатиÑніть тут, щоб відкрити меню тегів Ð´Ð»Ñ {tag}" #: src/components/RichText.tsx:191 msgid "Click here to open tag menu for #{tag}" -msgstr "" +msgstr "ÐатиÑніть тут, щоб відкрити меню тегів Ð´Ð»Ñ #{tag}" #: src/screens/Onboarding/index.tsx:35 msgid "Climate" -msgstr "" +msgstr "Клімат" #: src/view/com/modals/ChangePassword.tsx:265 #: src/view/com/modals/ChangePassword.tsx:268 msgid "Close" -msgstr "" +msgstr "Закрити" #: src/components/Dialog/index.web.tsx:84 #: src/components/Dialog/index.web.tsx:198 msgid "Close active dialog" -msgstr "" +msgstr "Закрити діалогове вікно" #: src/view/com/auth/login/PasswordUpdatedForm.tsx:38 msgid "Close alert" @@ -814,35 +753,35 @@ msgstr "Закрити панель навігації" #: src/components/TagMenu/index.tsx:262 msgid "Close this dialog" -msgstr "" +msgstr "Закрити діалогове вікно" #: src/view/shell/index.web.tsx:52 msgid "Closes bottom navigation bar" -msgstr "" +msgstr "Закриває нижню панель навігації" #: src/view/com/auth/login/PasswordUpdatedForm.tsx:39 msgid "Closes password update alert" -msgstr "" +msgstr "Закриває ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ" #: src/view/com/composer/Composer.tsx:309 msgid "Closes post composer and discards post draft" -msgstr "" +msgstr "Закриває редактор поÑтів Ñ– видалÑÑ” чернетку" #: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:27 msgid "Closes viewer for header image" -msgstr "" +msgstr "Закриває переглÑд зображеннÑ" #: src/view/com/notifications/FeedItem.tsx:318 msgid "Collapses list of users for a given notification" -msgstr "" +msgstr "Згортає ÑпиÑок кориÑтувачів Ð´Ð»Ñ Ð´Ð°Ð½Ð¾Ð³Ð¾ ÑповіщеннÑ" #: src/screens/Onboarding/index.tsx:41 msgid "Comedy" -msgstr "" +msgstr "КомедіÑ" #: src/screens/Onboarding/index.tsx:27 msgid "Comics" -msgstr "" +msgstr "КомікÑи" #: src/Navigation.tsx:229 #: src/view/screens/CommunityGuidelines.tsx:32 @@ -851,15 +790,15 @@ msgstr "Правила Ñпільноти" #: src/screens/Onboarding/StepFinished.tsx:148 msgid "Complete onboarding and start using your account" -msgstr "" +msgstr "Завершіть Ð¾Ð·Ð½Ð°Ð¹Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‚Ð° розпочніть кориÑтуватиÑÑ Ð²Ð°ÑˆÐ¸Ð¼ обліковим запиÑом" #: src/view/com/auth/create/Step3.tsx:73 msgid "Complete the challenge" -msgstr "" +msgstr "Виконайте завданнÑ" #: src/view/com/composer/Composer.tsx:424 msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" -msgstr "" +msgstr "Створюйте поÑти до {MAX_GRAPHEME_LENGTH} Ñимволів у довжину" #: src/view/com/composer/Prompt.tsx:24 msgid "Compose reply" @@ -867,7 +806,7 @@ msgstr "ВідповіÑти" #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:67 msgid "Configure content filtering setting for category: {0}" -msgstr "" +msgstr "Ðалаштувати Ñ„Ñ–Ð»ÑŒÑ‚Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¼Ñ–Ñту Ð´Ð»Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ñ–Ð¹: {0}" #: src/components/Prompt.tsx:124 #: src/view/com/modals/AppealLabel.tsx:98 @@ -883,7 +822,7 @@ msgstr "Підтвердити" #: src/view/com/modals/Confirm.tsx:78 msgctxt "action" msgid "Confirm" -msgstr "" +msgstr "Підтвердити" #: src/view/com/modals/ChangeEmail.tsx:193 #: src/view/com/modals/ChangeEmail.tsx:195 @@ -892,7 +831,7 @@ msgstr "Підтвердити" #: src/view/com/modals/lang-settings/ConfirmLanguagesButton.tsx:34 msgid "Confirm content language settings" -msgstr "Підтвердити перелік мов" +msgstr "Підтвердити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ð¾Ð²Ð¸ вміÑту" #: src/view/com/modals/DeleteAccount.tsx:220 msgid "Confirm delete account" @@ -900,7 +839,7 @@ msgstr "Підтвердити Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ Ð·Ð°Ð¿Ð¸Ñ #: src/view/com/modals/ContentFilteringSettings.tsx:156 msgid "Confirm your age to enable adult content." -msgstr "" +msgstr "Підтвердьте Ñвій вік, щоб дозволити вміÑÑ‚ Ð´Ð»Ñ Ð´Ð¾Ñ€Ð¾Ñлих." #: src/view/com/modals/ChangeEmail.tsx:157 #: src/view/com/modals/DeleteAccount.tsx:182 @@ -919,7 +858,7 @@ msgstr "З’єднаннÑ..." #: src/view/com/auth/create/CreateAccount.tsx:213 msgid "Contact support" -msgstr "" +msgstr "Служба підтримки" #: src/view/screens/Moderation.tsx:83 msgid "Content filtering" @@ -936,7 +875,7 @@ msgstr "Мови" #: src/view/com/modals/ModerationDetails.tsx:65 msgid "Content Not Available" -msgstr "" +msgstr "ВміÑÑ‚ недоÑтупний" #: src/view/com/modals/ModerationDetails.tsx:33 #: src/view/com/util/moderation/ScreenHider.tsx:78 @@ -955,26 +894,26 @@ msgstr "ÐŸÐ¾Ð¿ÐµÑ€ÐµÐ´Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ вміÑÑ‚" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:148 #: src/view/com/auth/onboarding/RecommendedFollows.tsx:209 msgid "Continue" -msgstr "Продовжити" +msgstr "Далі" #: src/screens/Onboarding/StepFollowingFeed.tsx:150 #: src/screens/Onboarding/StepInterests/index.tsx:245 #: src/screens/Onboarding/StepModeration/index.tsx:115 #: src/screens/Onboarding/StepTopicalFeeds.tsx:111 msgid "Continue to next step" -msgstr "" +msgstr "Перейти до наÑтупного кроку" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:167 msgid "Continue to the next step" -msgstr "" +msgstr "Перейти до наÑтупного кроку" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:191 msgid "Continue to the next step without following any accounts" -msgstr "" +msgstr "Перейдіть до наÑтупного кроку, ні на кого не підпиÑуючиÑÑŒ" #: src/screens/Onboarding/index.tsx:44 msgid "Cooking" -msgstr "" +msgstr "КухарÑтво" #: src/view/com/modals/AddAppPasswords.tsx:195 #: src/view/com/modals/InviteCodes.tsx:182 @@ -983,17 +922,17 @@ msgstr "Скопійовано" #: src/view/screens/Settings/index.tsx:241 msgid "Copied build version to clipboard" -msgstr "" +msgstr "ВерÑÑ–ÑŽ збірки Ñкопійовано до буфера обміну" #: src/view/com/modals/AddAppPasswords.tsx:76 #: src/view/com/modals/InviteCodes.tsx:152 #: src/view/com/util/forms/PostDropdownBtn.tsx:161 msgid "Copied to clipboard" -msgstr "" +msgstr "Скопійовано" #: src/view/com/modals/AddAppPasswords.tsx:189 msgid "Copies app password" -msgstr "" +msgstr "Копіює пароль заÑтоÑунку" #: src/view/com/modals/AddAppPasswords.tsx:188 msgid "Copy" @@ -1001,21 +940,21 @@ msgstr "Скопіювати" #: src/view/screens/ProfileList.tsx:418 msgid "Copy link to list" -msgstr "Скопіювати поÑиланнÑ" +msgstr "Копіювати поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° ÑпиÑок" #: src/view/com/util/forms/PostDropdownBtn.tsx:231 #: src/view/com/util/forms/PostDropdownBtn.tsx:237 msgid "Copy link to post" -msgstr "Скопіювати поÑиланнÑ" +msgstr "Копіювати поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° поÑÑ‚" #: src/view/com/profile/ProfileHeader.tsx:295 msgid "Copy link to profile" -msgstr "Скопіювати поÑиланнÑ" +msgstr "Копіювати поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° профіль" #: src/view/com/util/forms/PostDropdownBtn.tsx:223 #: src/view/com/util/forms/PostDropdownBtn.tsx:225 msgid "Copy post text" -msgstr "Скопіювати текÑÑ‚" +msgstr "Копіювати текÑÑ‚ повідомленнÑ" #: src/Navigation.tsx:234 #: src/view/screens/CopyrightPolicy.tsx:29 @@ -1042,7 +981,7 @@ msgstr "Створити новий обліковий запиÑ" #: src/view/screens/Settings/index.tsx:384 msgid "Create a new Bluesky account" -msgstr "" +msgstr "Створити новий обліковий Ð·Ð°Ð¿Ð¸Ñ Bluesky" #: src/view/com/auth/create/CreateAccount.tsx:133 msgid "Create Account" @@ -1050,7 +989,7 @@ msgstr "Створити обліковий запиÑ" #: src/view/com/modals/AddAppPasswords.tsx:226 msgid "Create App Password" -msgstr "" +msgstr "Створити пароль заÑтоÑунку" #: src/view/com/auth/HomeLoggedOutCTA.tsx:54 #: src/view/com/auth/SplashScreen.tsx:68 @@ -1063,24 +1002,24 @@ msgstr "Створено: {0}" #: src/view/screens/ProfileFeed.tsx:616 msgid "Created by <0/>" -msgstr "" +msgstr "Створено <0/>" #: src/view/screens/ProfileFeed.tsx:614 msgid "Created by you" -msgstr "" +msgstr "Створено вами" #: src/view/com/composer/Composer.tsx:455 msgid "Creates a card with a thumbnail. The card links to {url}" -msgstr "" +msgstr "Створює картку з мініатюрою. ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ ÐºÐ°Ñ€Ñ‚ÐºÐ¸: {url}" #: src/screens/Onboarding/index.tsx:29 msgid "Culture" -msgstr "" +msgstr "Культура" #: src/view/com/auth/server-input/index.tsx:95 #: src/view/com/auth/server-input/index.tsx:96 msgid "Custom" -msgstr "" +msgstr "КориÑтувацький" #: src/view/com/modals/ChangeHandle.tsx:389 msgid "Custom domain" @@ -1089,7 +1028,7 @@ msgstr "ВлаÑний домен" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 #: src/view/screens/Feeds.tsx:692 msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." -msgstr "" +msgstr "КаÑтомні Ñтрічки, Ñтворені Ñпільнотою, подарують вам нові Ð²Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ‚Ð° допоможуть знайти контент, Ñкий ви любите." #: src/view/screens/PreferencesExternalEmbeds.tsx:55 msgid "Customize media from external sites." @@ -1097,28 +1036,24 @@ msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼ÐµÐ´Ñ–Ð° зі Ñторонніх вебÑай #: src/view/screens/Settings.tsx:687 #~ msgid "Danger Zone" -#~ msgstr "Ðебезпечна зона" +#~ msgstr "" #: src/view/screens/Settings/index.tsx:485 #: src/view/screens/Settings/index.tsx:511 msgid "Dark" -msgstr "" +msgstr "Темна" #: src/view/screens/Debug.tsx:63 msgid "Dark mode" -msgstr "" +msgstr "Темний режим" #: src/view/screens/Settings/index.tsx:498 msgid "Dark Theme" -msgstr "" - -#: src/Navigation.tsx:204 -#~ msgid "Debug" -#~ msgstr "" +msgstr "Темна тема" #: src/view/screens/Debug.tsx:83 msgid "Debug panel" -msgstr "" +msgstr "Панель налагодженнÑ" #: src/view/screens/Settings/index.tsx:772 msgid "Delete account" @@ -1144,11 +1079,11 @@ msgstr "Видалити мій обліковий запиÑ" #: src/view/screens/Settings.tsx:706 #~ msgid "Delete my account…" -#~ msgstr "Видалити мій обліковий запиÑ…" +#~ msgstr "" #: src/view/screens/Settings/index.tsx:784 msgid "Delete My Account…" -msgstr "" +msgstr "Видалити мій обліковий запиÑ..." #: src/view/com/util/forms/PostDropdownBtn.tsx:317 #: src/view/com/util/forms/PostDropdownBtn.tsx:326 @@ -1161,7 +1096,7 @@ msgstr "Видалити цей поÑÑ‚?" #: src/view/com/util/post-embeds/QuoteEmbed.tsx:70 msgid "Deleted" -msgstr "" +msgstr "Видалено" #: src/view/com/post-thread/PostThread.tsx:316 msgid "Deleted post." @@ -1174,13 +1109,9 @@ msgstr "Видалений поÑÑ‚." msgid "Description" msgstr "ОпиÑ" -#: src/view/com/auth/create/Step1.tsx:96 -#~ msgid "Dev Server" -#~ msgstr "" - #: src/view/screens/Settings.tsx:760 #~ msgid "Developer Tools" -#~ msgstr "ІнÑтрументи розробника" +#~ msgstr "" #: src/view/com/composer/Composer.tsx:218 msgid "Did you want to say anything?" @@ -1188,7 +1119,7 @@ msgstr "Порожній поÑÑ‚. Ви хотіли щоÑÑŒ напиÑати?" #: src/view/screens/Settings/index.tsx:504 msgid "Dim" -msgstr "" +msgstr "ТьмÑний" #: src/view/com/composer/Composer.tsx:151 msgid "Discard" @@ -1205,15 +1136,15 @@ msgstr "ПопроÑити заÑтоÑунки не показувати мій #: src/view/com/posts/FollowingEmptyState.tsx:74 #: src/view/com/posts/FollowingEndOfFeed.tsx:75 msgid "Discover new custom feeds" -msgstr "" +msgstr "Відкрийте Ð´Ð»Ñ Ñебе нові Ñтрічки" #: src/view/screens/Feeds.tsx:473 #~ msgid "Discover new feeds" -#~ msgstr "Відкрийте Ð´Ð»Ñ Ñебе нові Ñтрічки" +#~ msgstr "" #: src/view/screens/Feeds.tsx:689 msgid "Discover New Feeds" -msgstr "" +msgstr "Відкрийте Ð´Ð»Ñ Ñебе нові Ñтрічки" #: src/view/com/modals/EditProfile.tsx:192 msgid "Display name" @@ -1242,7 +1173,7 @@ msgstr "Домен перевірено!" #: src/view/screens/PreferencesThreads.tsx:162 msgctxt "action" msgid "Done" -msgstr "" +msgstr "Готово" #: src/view/com/auth/server-input/index.tsx:165 #: src/view/com/auth/server-input/index.tsx:166 @@ -1266,48 +1197,48 @@ msgstr "Готово{extraText}" #: src/view/com/auth/login/ChooseAccountForm.tsx:45 msgid "Double tap to sign in" -msgstr "" +msgstr "Двічі натиÑніть, щоб увійти" #: src/view/screens/Settings/index.tsx:755 msgid "Download Bluesky account data (repository)" -msgstr "" +msgstr "Завантажити дані облікового запиÑу в Bluesky (репозиторій)" #: src/view/screens/Settings/ExportCarDialog.tsx:59 #: src/view/screens/Settings/ExportCarDialog.tsx:63 msgid "Download CAR file" -msgstr "" +msgstr "Завантажити CAR файл" #: src/view/com/composer/text-input/TextInput.web.tsx:249 msgid "Drop to add images" -msgstr "" +msgstr "ПеретÑгніть Ñ– відпуÑтіть, щоб додати зображеннÑ" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:111 msgid "Due to Apple policies, adult content can only be enabled on the web after completing sign up." -msgstr "" +msgstr "Через політику компанії Apple, переглÑд вміÑту Ð´Ð»Ñ Ð´Ð¾Ñ€Ð¾Ñлих можна ввімкнути лише в інтернеті піÑÐ»Ñ Ñ€ÐµÑ”Ñтрації." #: src/view/com/modals/EditProfile.tsx:185 msgid "e.g. Alice Roberts" -msgstr "" +msgstr "напр. Ð¢Ð°Ñ€Ð°Ñ Ð¨ÐµÐ²Ñ‡ÐµÐ½ÐºÐ¾" #: src/view/com/modals/EditProfile.tsx:203 msgid "e.g. Artist, dog-lover, and avid reader." -msgstr "" +msgstr "напр. ХудожницÑ, ÑÐ¾Ð±Ð°Ñ‡Ð½Ð¸Ñ†Ñ Ñ‚Ð° завзÑта читачка." #: src/view/com/modals/CreateOrEditList.tsx:283 msgid "e.g. Great Posters" -msgstr "" +msgstr "напр. Чудові пиÑарі" #: src/view/com/modals/CreateOrEditList.tsx:284 msgid "e.g. Spammers" -msgstr "" +msgstr "напр. Спамери" #: src/view/com/modals/CreateOrEditList.tsx:312 msgid "e.g. The posters who never miss." -msgstr "" +msgstr "напр. ПиÑарі, що нічого не пропуÑкають." #: src/view/com/modals/CreateOrEditList.tsx:313 msgid "e.g. Users that repeatedly reply with ads." -msgstr "" +msgstr "напр. КориÑтувачі, що неодноразово відповідали рекламою." #: src/view/com/modals/InviteCodes.tsx:96 msgid "Each code works once. You'll receive more invite codes periodically." @@ -1316,7 +1247,7 @@ msgstr "Кожен код Ð·Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ Ð¿Ñ€Ð°Ñ†ÑŽÑ” лише один Ñ€ #: src/view/com/lists/ListMembers.tsx:149 msgctxt "action" msgid "Edit" -msgstr "" +msgstr "Редагувати" #: src/view/com/composer/photos/Gallery.tsx:144 #: src/view/com/modals/EditImage.tsx:207 @@ -1329,7 +1260,7 @@ msgstr "Редагувати Ð¾Ð¿Ð¸Ñ ÑпиÑку" #: src/view/com/modals/CreateOrEditList.tsx:250 msgid "Edit Moderation List" -msgstr "" +msgstr "Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ ÑпиÑку" #: src/Navigation.tsx:244 #: src/view/screens/Feeds.tsx:434 @@ -1356,19 +1287,19 @@ msgstr "Редагувати збережені Ñтрічки" #: src/view/com/modals/CreateOrEditList.tsx:245 msgid "Edit User List" -msgstr "" +msgstr "Редагувати ÑпиÑок кориÑтувачів" #: src/view/com/modals/EditProfile.tsx:193 msgid "Edit your display name" -msgstr "" +msgstr "Редагувати ваш пÑевдонім Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ" #: src/view/com/modals/EditProfile.tsx:211 msgid "Edit your profile description" -msgstr "" +msgstr "Редагувати Ð¾Ð¿Ð¸Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ профілю" #: src/screens/Onboarding/index.tsx:34 msgid "Education" -msgstr "" +msgstr "ОÑвіта" #: src/view/com/auth/create/Step1.tsx:176 #: src/view/com/auth/login/ForgotPasswordForm.tsx:156 @@ -1384,7 +1315,7 @@ msgstr "ÐдреÑа електронної пошти" #: src/view/com/modals/ChangeEmail.tsx:56 #: src/view/com/modals/ChangeEmail.tsx:88 msgid "Email updated" -msgstr "" +msgstr "Електронну адреÑу змінено" #: src/view/com/modals/ChangeEmail.tsx:111 msgid "Email Updated" @@ -1392,7 +1323,7 @@ msgstr "Ел. адреÑу оновлено" #: src/view/com/modals/VerifyEmail.tsx:78 msgid "Email verified" -msgstr "" +msgstr "Електронну адреÑу перевірено" #: src/view/screens/Settings/index.tsx:312 msgid "Email:" @@ -1404,12 +1335,12 @@ msgstr "Увімкнути лише {0}" #: src/view/com/modals/ContentFilteringSettings.tsx:167 msgid "Enable Adult Content" -msgstr "" +msgstr "Дозволити вміÑÑ‚ Ð´Ð»Ñ Ð´Ð¾Ñ€Ð¾Ñлих" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:76 #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:77 msgid "Enable adult content in your feeds" -msgstr "" +msgstr "Увімкнути вміÑÑ‚ Ð´Ð»Ñ Ð´Ð¾Ñ€Ð¾Ñлих у ваших Ñтрічках" #: src/view/com/modals/EmbedConsent.tsx:97 msgid "Enable External Media" @@ -1417,11 +1348,11 @@ msgstr "Увімкнути зовнішні медіа" #: src/view/screens/PreferencesExternalEmbeds.tsx:75 msgid "Enable media players for" -msgstr "" +msgstr "Увімкнути медіапрогравачі длÑ" #: src/view/screens/PreferencesFollowingFeed.tsx:147 msgid "Enable this setting to only see replies between people you follow." -msgstr "Увімкніть цей параметр, щоб бачити відповіді тільки між людьми, на Ñких ви підпиÑані." +msgstr "Увімкніть цей параметр, щоб бачити відповіді тільки від людей, на Ñких ви підпиÑані." #: src/view/screens/Profile.tsx:455 msgid "End of feed" @@ -1429,24 +1360,20 @@ msgstr "Кінець Ñтрічки" #: src/view/com/modals/AddAppPasswords.tsx:166 msgid "Enter a name for this App Password" -msgstr "" +msgstr "Введіть ім'Ñ Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð·Ð°ÑтоÑунку" #: src/components/dialogs/MutedWords.tsx:100 #: src/components/dialogs/MutedWords.tsx:101 msgid "Enter a word or tag" -msgstr "" +msgstr "Введіть Ñлово або тег" #: src/view/com/modals/VerifyEmail.tsx:105 msgid "Enter Confirmation Code" -msgstr "" - -#: src/view/com/auth/create/Step1.tsx:71 -#~ msgid "Enter the address of your provider:" -#~ msgstr "Введіть адреÑу вашого хоÑтинг-провайдера:" +msgstr "Введіть код підтвердженнÑ" #: src/view/com/modals/ChangePassword.tsx:151 msgid "Enter the code you received to change your password." -msgstr "" +msgstr "Введіть код, Ñкий ви отримали, щоб змінити пароль." #: src/view/com/modals/ChangeHandle.tsx:371 msgid "Enter the domain you want to use" @@ -1459,7 +1386,7 @@ msgstr "Введіть адреÑу електронної пошти, Ñку в #: src/view/com/auth/create/Step1.tsx:228 #: src/view/com/modals/BirthDateSettings.tsx:74 msgid "Enter your birth date" -msgstr "" +msgstr "Введіть вашу дату народженнÑ" #: src/view/com/modals/Waitlist.tsx:78 #~ msgid "Enter your email" @@ -1471,7 +1398,7 @@ msgstr "Введіть адреÑу електронної пошти" #: src/view/com/modals/ChangeEmail.tsx:41 msgid "Enter your new email above" -msgstr "" +msgstr "Введіть вашу нову електронну пошту вище" #: src/view/com/modals/ChangeEmail.tsx:117 msgid "Enter your new email address below." @@ -1487,7 +1414,7 @@ msgstr "Введіть пÑевдонім та пароль" #: src/view/com/auth/create/Step3.tsx:67 msgid "Error receiving captcha response." -msgstr "" +msgstr "Помилка Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ñ– Captcha." #: src/view/screens/Search/Search.tsx:110 msgid "Error:" @@ -1499,16 +1426,16 @@ msgstr "УÑÑ–" #: src/view/com/modals/ChangeHandle.tsx:150 msgid "Exits handle change process" -msgstr "" +msgstr "Вихід з процеÑу зміни пÑевдоніму кориÑтувача" #: src/view/com/lightbox/Lightbox.web.tsx:120 msgid "Exits image view" -msgstr "" +msgstr "Вийти з режиму переглÑду" #: src/view/com/modals/ListAddRemoveUsers.tsx:88 #: src/view/shell/desktop/Search.tsx:235 msgid "Exits inputting search query" -msgstr "" +msgstr "Вихід із пошуку" #: src/view/com/modals/Waitlist.tsx:138 #~ msgid "Exits signing up for waitlist with {email}" @@ -1516,21 +1443,21 @@ msgstr "" #: src/view/com/lightbox/Lightbox.web.tsx:163 msgid "Expand alt text" -msgstr "Розгорнути альтернативний текÑÑ‚" +msgstr "Розгорнути опиÑ" #: src/view/com/composer/ComposerReplyTo.tsx:81 #: src/view/com/composer/ComposerReplyTo.tsx:84 msgid "Expand or collapse the full post you are replying to" -msgstr "" +msgstr "Розгорнути або згорнути веÑÑŒ поÑÑ‚, на Ñкий ви відповідаєте" #: src/view/screens/Settings/index.tsx:753 msgid "Export my data" -msgstr "" +msgstr "ЕкÑпорт моїх даних" #: src/view/screens/Settings/ExportCarDialog.tsx:44 #: src/view/screens/Settings/index.tsx:764 msgid "Export My Data" -msgstr "" +msgstr "ЕкÑпорт моїх даних" #: src/view/com/modals/EmbedConsent.tsx:64 msgid "External Media" @@ -1549,20 +1476,20 @@ msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ–Ñ… медіа" #: src/view/screens/Settings/index.tsx:648 msgid "External media settings" -msgstr "" +msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ–Ñ… медіа" #: src/view/com/modals/AddAppPasswords.tsx:115 #: src/view/com/modals/AddAppPasswords.tsx:119 msgid "Failed to create app password." -msgstr "" +msgstr "Ðе вдалоÑÑ Ñтворити пароль заÑтоÑунку." #: src/view/com/modals/CreateOrEditList.tsx:206 msgid "Failed to create the list. Check your internet connection and try again." -msgstr "" +msgstr "Ðе вдалоÑÑ Ñтворити ÑпиÑок. Перевірте інтернет-з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñ– Ñпробуйте ще раз." #: src/view/com/util/forms/PostDropdownBtn.tsx:128 msgid "Failed to delete post, please try again" -msgstr "" +msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ поÑÑ‚, Ñпробуйте ще раз" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:109 #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:141 @@ -1571,11 +1498,11 @@ msgstr "Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ рекомендації ÑÑ‚ #: src/Navigation.tsx:194 msgid "Feed" -msgstr "" +msgstr "Стрічка" #: src/view/com/feeds/FeedSourceCard.tsx:231 msgid "Feed by {0}" -msgstr "" +msgstr "Стрічка від {0}" #: src/view/screens/Feeds.tsx:605 msgid "Feed offline" @@ -1583,12 +1510,12 @@ msgstr "Стрічка не працює" #: src/view/com/feeds/FeedPage.tsx:143 #~ msgid "Feed Preferences" -#~ msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñтрічки" +#~ msgstr "" #: src/view/shell/desktop/RightNav.tsx:61 #: src/view/shell/Drawer.tsx:311 msgid "Feedback" -msgstr "ÐадіÑлати відгук" +msgstr "Зворотний зв'Ñзок" #: src/Navigation.tsx:452 #: src/view/screens/Feeds.tsx:419 @@ -1615,21 +1542,21 @@ msgstr "Стрічки ÑтворюютьÑÑ ÐºÐ¾Ñ€Ð¸Ñтувачами Ð´Ð»Ñ #: src/view/screens/SavedFeeds.tsx:156 msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." -msgstr "Стрічки - це алгоритми, Ñтворені кориÑтувачами з деÑким доÑвідом програмуваннÑ. <0/> Ð´Ð»Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації." +msgstr "Стрічки – це алгоритми, Ñтворені кориÑтувачами з деÑким доÑвідом програмуваннÑ. <0/> Ð´Ð»Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації." #: src/screens/Onboarding/StepTopicalFeeds.tsx:76 msgid "Feeds can be topical as well!" -msgstr "" +msgstr "Стрічки також можуть бути тематичними!" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Finalizing" -msgstr "" +msgstr "ЗавершеннÑ" #: src/view/com/posts/CustomFeedEmptyState.tsx:47 #: src/view/com/posts/FollowingEmptyState.tsx:57 #: src/view/com/posts/FollowingEndOfFeed.tsx:58 msgid "Find accounts to follow" -msgstr "" +msgstr "Знайдіть облікові запиÑи Ð´Ð»Ñ ÑтеженнÑ" #: src/view/screens/Search/Search.tsx:440 msgid "Find users on Bluesky" @@ -1637,19 +1564,19 @@ msgstr "Знайти кориÑтувачів у Bluesky" #: src/view/screens/Search/Search.tsx:438 msgid "Find users with the search tool on the right" -msgstr "Знайти кориÑтувачів за допомогою Ð¿Ð¾Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ Ñправа вгорі" +msgstr "Знайдіть кориÑтувачів за допомогою інÑтрументу пошуку праворуч" #: src/view/com/auth/onboarding/RecommendedFollowsItem.tsx:150 msgid "Finding similar accounts..." -msgstr "Пошук Ñхожих кориÑтувачів..." +msgstr "Пошук подібних облікових запиÑів..." #: src/view/screens/PreferencesFollowingFeed.tsx:111 msgid "Fine-tune the content you see on your Following feed." -msgstr "" +msgstr "Оберіть, що ви хочете бачити у Ñвоїй Ñтрічці підпиÑок." #: src/view/screens/PreferencesHomeFeed.tsx:111 #~ msgid "Fine-tune the content you see on your home screen." -#~ msgstr "Оберіть, що ви хочете бачити у Ñвоїй домашній Ñтрічці." +#~ msgstr "" #: src/view/screens/PreferencesThreads.tsx:60 msgid "Fine-tune the discussion threads." @@ -1657,20 +1584,20 @@ msgstr "Ðалаштуйте Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½ÑŒ." #: src/screens/Onboarding/index.tsx:38 msgid "Fitness" -msgstr "" +msgstr "ФітнеÑ" #: src/screens/Onboarding/StepFinished.tsx:131 msgid "Flexible" -msgstr "" +msgstr "Гнучкий" #: src/view/com/modals/EditImage.tsx:115 msgid "Flip horizontal" -msgstr "" +msgstr "Віддзеркалити горизонтально" #: src/view/com/modals/EditImage.tsx:120 #: src/view/com/modals/EditImage.tsx:287 msgid "Flip vertically" -msgstr "" +msgstr "Віддзеркалити вертикально" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:181 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:136 @@ -1681,29 +1608,21 @@ msgstr "ПідпиÑатиÑÑ" #: src/view/com/profile/FollowButton.tsx:64 msgctxt "action" msgid "Follow" -msgstr "" +msgstr "ПідпиÑатиÑÑŒ" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:58 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:122 #: src/view/com/profile/ProfileHeader.tsx:504 msgid "Follow {0}" -msgstr "" +msgstr "ПідпиÑатиÑÑ Ð½Ð° {0}" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:179 msgid "Follow All" -msgstr "" +msgstr "ПідпиÑатиÑÑ Ð½Ð° вÑÑ–Ñ…" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:174 msgid "Follow selected accounts and continue to the next step" -msgstr "" - -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:174 -#~ msgid "Follow selected accounts and continue to then next step" -#~ msgstr "" - -#: src/view/com/auth/onboarding/RecommendedFollows.tsx:42 -#~ msgid "Follow some" -#~ msgstr "" +msgstr "ПідпишітьÑÑ Ð½Ð° обрані облікові запиÑи Ñ– переходьте до наÑтупного кроку" #: src/view/com/auth/onboarding/RecommendedFollows.tsx:64 msgid "Follow some users to get started. We can recommend you more users based on who you find interesting." @@ -1711,7 +1630,7 @@ msgstr "ПідпишітьÑÑ Ð½Ð° кількох кориÑтувачів що #: src/view/com/profile/ProfileCard.tsx:194 msgid "Followed by {0}" -msgstr "" +msgstr "ПідпиÑані {0}" #: src/view/com/modals/Threadgate.tsx:98 msgid "Followed users" @@ -1723,16 +1642,12 @@ msgstr "Тільки ваші підпиÑки" #: src/view/com/notifications/FeedItem.tsx:166 msgid "followed you" -msgstr "" +msgstr "підпиÑка на ваÑ" #: src/view/screens/ProfileFollowers.tsx:25 msgid "Followers" msgstr "ПідпиÑники" -#: src/view/com/profile/ProfileHeader.tsx:624 -#~ msgid "following" -#~ msgstr "підпиÑок" - #: src/view/com/post-thread/PostThreadFollowBtn.tsx:136 #: src/view/com/profile/ProfileHeader.tsx:495 #: src/view/screens/ProfileFollows.tsx:25 @@ -1741,7 +1656,7 @@ msgstr "ПідпиÑані" #: src/view/com/profile/ProfileHeader.tsx:149 msgid "Following {0}" -msgstr "" +msgstr "ПідпиÑÐ°Ð½Ð½Ñ Ð½Ð° \"{0}\"" #: src/Navigation.tsx:250 #: src/view/com/home/HomeHeaderLayout.web.tsx:50 @@ -1749,7 +1664,7 @@ msgstr "" #: src/view/screens/PreferencesFollowingFeed.tsx:104 #: src/view/screens/Settings/index.tsx:543 msgid "Following Feed Preferences" -msgstr "" +msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñтрічки підпиÑок" #: src/view/com/profile/ProfileHeader.tsx:546 msgid "Follows you" @@ -1757,11 +1672,11 @@ msgstr "ПідпиÑаний(-на) на ваÑ" #: src/view/com/profile/ProfileCard.tsx:141 msgid "Follows You" -msgstr "" +msgstr "ПідпиÑаний(-на) на ваÑ" #: src/screens/Onboarding/index.tsx:43 msgid "Food" -msgstr "" +msgstr "Їжа" #: src/view/com/modals/DeleteAccount.tsx:111 msgid "For security reasons, we'll need to send a confirmation code to your email address." @@ -1787,12 +1702,12 @@ msgstr "Забули пароль" #: src/screens/Hashtag.tsx:108 #: src/screens/Hashtag.tsx:148 msgid "From @{sanitizedAuthor}" -msgstr "" +msgstr "Від @{sanitizedAuthor}" #: src/view/com/posts/FeedItem.tsx:189 msgctxt "from-feed" msgid "From <0/>" -msgstr "" +msgstr "Зі Ñтрічки \"<0/>\"" #: src/view/com/composer/photos/SelectPhotoBtn.tsx:43 msgid "Gallery" @@ -1820,12 +1735,12 @@ msgstr "Ðазад" #: src/screens/Onboarding/Layout.tsx:104 #: src/screens/Onboarding/Layout.tsx:193 msgid "Go back to previous step" -msgstr "" +msgstr "ПовернутиÑÑ Ð´Ð¾ попереднього кроку" #: src/view/screens/Search/Search.tsx:747 #: src/view/shell/desktop/Search.tsx:262 msgid "Go to @{queryMaybeHandle}" -msgstr "" +msgstr "Перейти до @{queryMaybeHandle}" #: src/view/com/auth/login/ForgotPasswordForm.tsx:189 #: src/view/com/auth/login/ForgotPasswordForm.tsx:218 @@ -1841,7 +1756,7 @@ msgstr "ПÑевдонім" #: src/Navigation.tsx:270 msgid "Hashtag" -msgstr "" +msgstr "Хештег" #: src/components/RichText.tsx:188 #~ msgid "Hashtag: {tag}" @@ -1849,11 +1764,11 @@ msgstr "" #: src/components/RichText.tsx:190 msgid "Hashtag: #{tag}" -msgstr "" +msgstr "Хештег: #{tag}" #: src/view/com/auth/create/CreateAccount.tsx:208 msgid "Having trouble?" -msgstr "" +msgstr "Виникли проблеми?" #: src/view/shell/desktop/RightNav.tsx:90 #: src/view/shell/Drawer.tsx:321 @@ -1862,19 +1777,15 @@ msgstr "Довідка" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:132 msgid "Here are some accounts for you to follow" -msgstr "" - -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:132 -#~ msgid "Here are some accounts for your to follow" -#~ msgstr "" +msgstr "ОÑÑŒ деÑкі облікові запиÑи, на Ñкі ви підпиÑані" #: src/screens/Onboarding/StepTopicalFeeds.tsx:85 msgid "Here are some popular topical feeds. You can choose to follow as many as you like." -msgstr "" +msgstr "ОÑÑŒ декілька популÑрних тематичних Ñтрічок. Ви можете підпиÑатиÑÑ Ð½Ð° Ñкільки забажаєте з них." #: src/screens/Onboarding/StepTopicalFeeds.tsx:80 msgid "Here are some topical feeds based on your interests: {interestsText}. You can choose to follow as many as you like." -msgstr "" +msgstr "ОÑÑŒ декілька тематичних Ñтрічок на оÑнові ваших інтереÑів: {interestsText}. Ви можете підпиÑатиÑÑ Ð½Ð° Ñкільки забажаєте з них." #: src/view/com/modals/AddAppPasswords.tsx:153 msgid "Here is your app password." @@ -1891,33 +1802,29 @@ msgstr "Приховати" #: src/view/com/notifications/FeedItem.tsx:326 msgctxt "action" msgid "Hide" -msgstr "" +msgstr "Сховати" #: src/view/com/util/forms/PostDropdownBtn.tsx:276 #: src/view/com/util/forms/PostDropdownBtn.tsx:287 msgid "Hide post" -msgstr "Приховати поÑÑ‚" +msgstr "Сховати поÑÑ‚" #: src/view/com/util/moderation/ContentHider.tsx:67 #: src/view/com/util/moderation/PostHider.tsx:61 msgid "Hide the content" -msgstr "" +msgstr "Приховати вміÑÑ‚" #: src/view/com/util/forms/PostDropdownBtn.tsx:280 msgid "Hide this post?" -msgstr "Приховати цей поÑÑ‚?" +msgstr "Сховати цей поÑÑ‚?" #: src/view/com/notifications/FeedItem.tsx:316 msgid "Hide user list" -msgstr "Приховати ÑпиÑок кориÑтувачів" +msgstr "Сховати ÑпиÑок кориÑтувачів" #: src/view/com/profile/ProfileHeader.tsx:487 msgid "Hides posts from {0} in your feed" -msgstr "" - -#: src/view/com/posts/FeedErrorMessage.tsx:102 -#~ msgid "Hmm, some kind of issue occured when contacting the feed server. Please let the feed owner know about this issue." -#~ msgstr "" +msgstr "Приховує поÑти з {0} у вашій Ñтрічці" #: src/view/com/posts/FeedErrorMessage.tsx:111 msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." @@ -1939,10 +1846,6 @@ msgstr "Хм, Ñервер Ñтрічки надіÑлав нам незрозу msgid "Hmm, we're having trouble finding this feed. It may have been deleted." msgstr "Хм, ми не можемо знайти цю Ñтрічку. Можливо вона була видалена." -#: src/view/com/posts/FeedErrorMessage.tsx:87 -#~ msgid "Hmmm, we're having trouble finding this feed. It may have been deleted." -#~ msgstr "" - #: src/Navigation.tsx:442 #: src/view/shell/bottom-bar/BottomBar.tsx:137 #: src/view/shell/desktop/LeftNav.tsx:306 @@ -1956,21 +1859,16 @@ msgstr "Головна" #: src/view/screens/PreferencesHomeFeed.tsx:104 #: src/view/screens/Settings/index.tsx:543 #~ msgid "Home Feed Preferences" -#~ msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾Ð¼Ð°ÑˆÐ½ÑŒÐ¾Ñ— Ñтрічки" +#~ msgstr "" #: src/view/com/auth/create/Step1.tsx:75 #: src/view/com/auth/login/ForgotPasswordForm.tsx:120 msgid "Hosting provider" msgstr "ХоÑтинг-провайдер" -#: src/view/com/auth/create/Step1.tsx:76 -#: src/view/com/auth/create/Step1.tsx:81 -#~ msgid "Hosting provider address" -#~ msgstr "ÐдреÑа хоÑтинг-провайдера" - #: src/view/com/modals/InAppBrowserConsent.tsx:44 msgid "How should we open this link?" -msgstr "" +msgstr "Як ви хочете відкрити це поÑиланнÑ?" #: src/view/com/modals/VerifyEmail.tsx:214 msgid "I have a code" @@ -1978,7 +1876,7 @@ msgstr "У мене Ñ” код" #: src/view/com/modals/VerifyEmail.tsx:216 msgid "I have a confirmation code" -msgstr "" +msgstr "У мене Ñ” код підтвердженнÑ" #: src/view/com/modals/ChangeHandle.tsx:283 msgid "I have my own domain" @@ -1986,7 +1884,7 @@ msgstr "Я маю влаÑний домен" #: src/view/com/lightbox/Lightbox.web.tsx:165 msgid "If alt text is long, toggles alt text expanded state" -msgstr "" +msgstr "Розкриває альтернативний текÑÑ‚, Ñкщо текÑÑ‚ задовгий" #: src/view/com/modals/SelfLabel.tsx:127 msgid "If none are selected, suitable for all ages." @@ -1994,61 +1892,48 @@ msgstr "Якщо не вибрано жодного варіанту - підхР#: src/view/com/modals/ChangePassword.tsx:146 msgid "If you want to change your password, we will send you a code to verify that this is your account." -msgstr "" +msgstr "Якщо ви хочете змінити пароль, ми надішлемо вам код, щоб переконатиÑÑ, що це ваш обліковий запиÑ." #: src/view/com/util/images/Gallery.tsx:38 msgid "Image" -msgstr "" +msgstr "ЗображеннÑ" #: src/view/com/modals/AltImage.tsx:120 msgid "Image alt text" -msgstr "Ðльтернативний текÑÑ‚" +msgstr "ÐžÐ¿Ð¸Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ" #: src/view/com/util/UserAvatar.tsx:311 #: src/view/com/util/UserBanner.tsx:118 msgid "Image options" msgstr "Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ" -#: src/view/com/search/Suggestions.tsx:104 -#: src/view/com/search/Suggestions.tsx:115 -#~ msgid "In Your Network" -#~ msgstr "" - #: src/view/com/auth/login/SetNewPasswordForm.tsx:138 msgid "Input code sent to your email for password reset" -msgstr "" +msgstr "Введіть код, надіÑланий на вашу електронну пошту Ð´Ð»Ñ ÑÐºÐ¸Ð´Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ" #: src/view/com/modals/DeleteAccount.tsx:184 msgid "Input confirmation code for account deletion" -msgstr "" +msgstr "Введіть код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу" #: src/view/com/auth/create/Step1.tsx:177 msgid "Input email for Bluesky account" -msgstr "" - -#: src/view/com/auth/create/Step2.tsx:109 -#~ msgid "Input email for Bluesky waitlist" -#~ msgstr "" - -#: src/view/com/auth/create/Step1.tsx:80 -#~ msgid "Input hosting provider address" -#~ msgstr "" +msgstr "Введіть адреÑу електронної пошти Ð´Ð»Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу Bluesky" #: src/view/com/auth/create/Step1.tsx:151 msgid "Input invite code to proceed" -msgstr "" +msgstr "Введіть код запрошеннÑ, щоб продовжити" #: src/view/com/modals/AddAppPasswords.tsx:180 msgid "Input name for app password" -msgstr "" +msgstr "Введіть ім'Ñ Ð´Ð»Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð·Ð°ÑтоÑунку" #: src/view/com/auth/login/SetNewPasswordForm.tsx:162 msgid "Input new password" -msgstr "" +msgstr "Введіть новий пароль" #: src/view/com/modals/DeleteAccount.tsx:203 msgid "Input password for account deletion" -msgstr "" +msgstr "Введіть пароль Ð´Ð»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу" #: src/view/com/auth/create/Step2.tsx:196 #~ msgid "Input phone number for SMS verification" @@ -2056,11 +1941,11 @@ msgstr "" #: src/view/com/auth/login/LoginForm.tsx:230 msgid "Input the password tied to {identifier}" -msgstr "" +msgstr "Введіть пароль, прив'Ñзаний до {identifier}" #: src/view/com/auth/login/LoginForm.tsx:197 msgid "Input the username or email address you used at signup" -msgstr "" +msgstr "Введіть пÑевдонім або ел. адреÑу, Ñкі ви викориÑтовували Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації" #: src/view/com/auth/create/Step2.tsx:271 #~ msgid "Input the verification code we have texted to you" @@ -2072,15 +1957,15 @@ msgstr "" #: src/view/com/auth/login/LoginForm.tsx:229 msgid "Input your password" -msgstr "" +msgstr "Введіть ваш пароль" #: src/view/com/auth/create/Step2.tsx:80 msgid "Input your user handle" -msgstr "" +msgstr "Введіть ваш пÑевдонім" #: src/view/com/post-thread/PostThreadItem.tsx:226 msgid "Invalid or unsupported post record" -msgstr "" +msgstr "Ðевірний або непідтримуваний поÑÑ‚" #: src/view/com/auth/login/LoginForm.tsx:113 msgid "Invalid username or password" @@ -2088,7 +1973,7 @@ msgstr "Ðевірне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача або пароль" #: src/view/screens/Settings.tsx:411 #~ msgid "Invite" -#~ msgstr "ЗапроÑити" +#~ msgstr "" #: src/view/com/modals/InviteCodes.tsx:93 msgid "Invite a Friend" @@ -2105,19 +1990,19 @@ msgstr "Код Ð·Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ Ð½Ðµ прийнÑто. ПереконайтР#: src/view/com/modals/InviteCodes.tsx:170 msgid "Invite codes: {0} available" -msgstr "" +msgstr "Коди запрошеннÑ: {0}" #: src/view/shell/Drawer.tsx:645 #~ msgid "Invite codes: {invitesAvailable} available" -#~ msgstr "Коди запрошеннÑ: {invitesAvailable}" +#~ msgstr "" #: src/view/com/modals/InviteCodes.tsx:169 msgid "Invite codes: 1 available" -msgstr "" +msgstr "Коди запрошеннÑ: 1" #: src/screens/Onboarding/StepFollowingFeed.tsx:64 msgid "It shows posts from the people you follow as they happen." -msgstr "" +msgstr "Ми показуємо поÑти людей, за Ñкими ви Ñлідкуєте в тому порÑдку в Ñкому вони публікуютьÑÑ." #: src/view/com/auth/HomeLoggedOutCTA.tsx:99 #: src/view/com/auth/SplashScreen.web.tsx:138 @@ -2126,20 +2011,20 @@ msgstr "ВаканÑÑ–Ñ—" #: src/view/com/modals/Waitlist.tsx:67 #~ msgid "Join the waitlist" -#~ msgstr "ПриєднатиÑÑ Ð´Ð¾ черги очікуваннÑ" +#~ msgstr "" #: src/view/com/auth/create/Step1.tsx:174 #: src/view/com/auth/create/Step1.tsx:178 #~ msgid "Join the waitlist." -#~ msgstr "ПриєднатиÑÑ Ð´Ð¾ черги очікуваннÑ." +#~ msgstr "" #: src/view/com/modals/Waitlist.tsx:128 #~ msgid "Join Waitlist" -#~ msgstr "ПриєднатиÑÑ Ð´Ð¾ черги очікуваннÑ" +#~ msgstr "" #: src/screens/Onboarding/index.tsx:24 msgid "Journalism" -msgstr "" +msgstr "ЖурналіÑтика" #: src/view/com/composer/select-language/SelectLangBtn.tsx:104 msgid "Language selection" @@ -2147,7 +2032,7 @@ msgstr "Вибір мови" #: src/view/screens/Settings/index.tsx:594 msgid "Language settings" -msgstr "" +msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ð¾Ð²Ð¸" #: src/Navigation.tsx:142 #: src/view/screens/LanguageSettings.tsx:89 @@ -2160,7 +2045,7 @@ msgstr "Мови" #: src/view/com/auth/create/StepHeader.tsx:20 msgid "Last step!" -msgstr "" +msgstr "ОÑтанній крок!" #: src/view/com/util/moderation/ContentHider.tsx:103 msgid "Learn more" @@ -2194,11 +2079,11 @@ msgstr "Ви залишаєте Bluesky" #: src/screens/Deactivated.tsx:128 msgid "left to go." -msgstr "" +msgstr "ще залишилоÑÑ." #: src/view/screens/Settings/index.tsx:278 msgid "Legacy storage cleared, you need to restart the app now." -msgstr "" +msgstr "Старе Ñховище очищено, тепер вам потрібно перезапуÑтити заÑтоÑунок." #: src/view/com/auth/login/Login.tsx:128 #: src/view/com/auth/login/Login.tsx:144 @@ -2207,7 +2092,7 @@ msgstr "Давайте відновимо ваш пароль!" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Let's go!" -msgstr "" +msgstr "Злітаємо!" #: src/view/com/util/UserAvatar.tsx:248 #: src/view/com/util/UserBanner.tsx:62 @@ -2216,11 +2101,11 @@ msgstr "ГалереÑ" #: src/view/screens/Settings/index.tsx:479 msgid "Light" -msgstr "" +msgstr "Світла" #: src/view/com/util/post-ctrls/PostCtrls.tsx:182 msgid "Like" -msgstr "" +msgstr "Вподобати" #: src/view/screens/ProfileFeed.tsx:591 msgid "Like this feed" @@ -2233,31 +2118,23 @@ msgstr "СподобалоÑÑ" #: src/view/screens/PostLikedBy.tsx:27 #: src/view/screens/ProfileFeedLikedBy.tsx:27 msgid "Liked By" -msgstr "" +msgstr "СподобавÑÑ ÐºÐ¾Ñ€Ð¸Ñтувачу" #: src/view/com/feeds/FeedSourceCard.tsx:279 msgid "Liked by {0} {1}" -msgstr "" +msgstr "Вподобано {0} {1}" #: src/view/screens/ProfileFeed.tsx:606 msgid "Liked by {likeCount} {0}" -msgstr "" +msgstr "Вподобано {likeCount} {0}" #: src/view/com/notifications/FeedItem.tsx:170 msgid "liked your custom feed" -msgstr "" - -#: src/view/com/notifications/FeedItem.tsx:171 -#~ msgid "liked your custom feed '{0}'" -#~ msgstr "" - -#: src/view/com/notifications/FeedItem.tsx:171 -#~ msgid "liked your custom feed{0}" -#~ msgstr "" +msgstr "вподобав(-ла) вашу Ñтрічку" #: src/view/com/notifications/FeedItem.tsx:155 msgid "liked your post" -msgstr "" +msgstr "ÑподобавÑÑ Ð²Ð°Ñˆ поÑÑ‚" #: src/view/screens/Profile.tsx:183 msgid "Likes" @@ -2265,19 +2142,11 @@ msgstr "ВподобаннÑ" #: src/view/com/post-thread/PostThreadItem.tsx:183 msgid "Likes on this post" -msgstr "" - -#: src/view/screens/Moderation.tsx:203 -#~ msgid "Limit the visibility of my account" -#~ msgstr "" - -#: src/view/screens/Moderation.tsx:203 -#~ msgid "Limit the visibility of my account to logged-out users" -#~ msgstr "" +msgstr "Вподобайки цього поÑта" #: src/Navigation.tsx:168 msgid "List" -msgstr "" +msgstr "СпиÑок" #: src/view/com/modals/CreateOrEditList.tsx:261 msgid "List Avatar" @@ -2285,19 +2154,19 @@ msgstr "Ðватар ÑпиÑку" #: src/view/screens/ProfileList.tsx:324 msgid "List blocked" -msgstr "" +msgstr "СпиÑок заблоковано" #: src/view/com/feeds/FeedSourceCard.tsx:233 msgid "List by {0}" -msgstr "" +msgstr "СпиÑок від {0}" #: src/view/screens/ProfileList.tsx:378 msgid "List deleted" -msgstr "" +msgstr "СпиÑок видалено" #: src/view/screens/ProfileList.tsx:283 msgid "List muted" -msgstr "" +msgstr "СпиÑок ігноруєтьÑÑ" #: src/view/com/modals/CreateOrEditList.tsx:275 msgid "List Name" @@ -2305,11 +2174,11 @@ msgstr "Ðазва ÑпиÑку" #: src/view/screens/ProfileList.tsx:343 msgid "List unblocked" -msgstr "" +msgstr "СпиÑок розблоковано" #: src/view/screens/ProfileList.tsx:302 msgid "List unmuted" -msgstr "" +msgstr "СпиÑок більше не ігноруєтьÑÑ" #: src/Navigation.tsx:112 #: src/view/screens/Profile.tsx:185 @@ -2341,22 +2210,18 @@ msgstr "ЗавантаженнÑ..." #: src/view/com/modals/ServerInput.tsx:50 #~ msgid "Local dev server" -#~ msgstr "Локальний Ñервер розробки" +#~ msgstr "" #: src/Navigation.tsx:209 msgid "Log" -msgstr "" +msgstr "Звіт" #: src/screens/Deactivated.tsx:149 #: src/screens/Deactivated.tsx:152 #: src/screens/Deactivated.tsx:178 #: src/screens/Deactivated.tsx:181 msgid "Log out" -msgstr "" - -#: src/view/screens/Moderation.tsx:134 -#~ msgid "Logged-out users" -#~ msgstr "" +msgstr "Вийти" #: src/view/screens/Moderation.tsx:155 msgid "Logged-out visibility" @@ -2366,25 +2231,21 @@ msgstr "ВидиміÑть Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів без обліков msgid "Login to account that is not listed" msgstr "Увійти до облікового запиÑу, Ñкого немає в ÑпиÑку" -#: src/view/screens/ProfileFeed.tsx:472 -#~ msgid "Looks like this feed is only available to users with a Bluesky account. Please sign up or sign in to view this feed!" -#~ msgstr "" - #: src/view/com/modals/LinkWarning.tsx:65 msgid "Make sure this is where you intend to go!" msgstr "ПереконайтеÑÑ, що це дійÑно той Ñайт, що ви збираєтеÑÑ Ð²Ñ–Ð´Ð²Ñ–Ð´Ð°Ñ‚Ð¸!" #: src/components/dialogs/MutedWords.tsx:83 msgid "Manage your muted words and tags" -msgstr "" +msgstr "Ðалаштовуйте ваші ігноровані Ñлова та теги" #: src/view/com/auth/create/Step2.tsx:118 msgid "May not be longer than 253 characters" -msgstr "" +msgstr "Ðе може бути довшим за 253 Ñимволи" #: src/view/com/auth/create/Step2.tsx:109 msgid "May only contain letters and numbers" -msgstr "" +msgstr "Може міÑтити лише літери та цифри" #: src/view/screens/Profile.tsx:182 msgid "Media" @@ -2403,13 +2264,9 @@ msgstr "Згадані кориÑтувачі" msgid "Menu" msgstr "Меню" -#: src/view/com/posts/FeedErrorMessage.tsx:194 -#~ msgid "Message from server" -#~ msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ Ñервера" - #: src/view/com/posts/FeedErrorMessage.tsx:197 msgid "Message from server: {0}" -msgstr "" +msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ Ñервера: {0}" #: src/Navigation.tsx:117 #: src/view/screens/Moderation.tsx:66 @@ -2423,25 +2280,25 @@ msgstr "МодераціÑ" #: src/view/com/lists/ListCard.tsx:93 #: src/view/com/modals/UserAddRemoveLists.tsx:206 msgid "Moderation list by {0}" -msgstr "" +msgstr "СпиÑок модерації від {0}" #: src/view/screens/ProfileList.tsx:775 msgid "Moderation list by <0/>" -msgstr "" +msgstr "СпиÑок модерації від <0/>" #: src/view/com/lists/ListCard.tsx:91 #: src/view/com/modals/UserAddRemoveLists.tsx:204 #: src/view/screens/ProfileList.tsx:773 msgid "Moderation list by you" -msgstr "" +msgstr "СпиÑок модерації від ваÑ" #: src/view/com/modals/CreateOrEditList.tsx:197 msgid "Moderation list created" -msgstr "" +msgstr "СпиÑок модерації Ñтворено" #: src/view/com/modals/CreateOrEditList.tsx:183 msgid "Moderation list updated" -msgstr "" +msgstr "СпиÑок модерації оновлено" #: src/view/screens/Moderation.tsx:114 msgid "Moderation lists" @@ -2454,11 +2311,11 @@ msgstr "СпиÑки Ð´Ð»Ñ Ð¼Ð¾Ð´ÐµÑ€Ð°Ñ†Ñ–Ñ—" #: src/view/screens/Settings/index.tsx:619 msgid "Moderation settings" -msgstr "" +msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ð¾Ð´ÐµÑ€Ð°Ñ†Ñ–Ñ—" #: src/view/com/modals/ModerationDetails.tsx:35 msgid "Moderator has chosen to set a general warning on the content." -msgstr "" +msgstr "Модератор вирішив вÑтановити загальне Ð¿Ð¾Ð¿ÐµÑ€ÐµÐ´Ð¶ÐµÐ½Ð½Ñ Ð½Ð° вміÑÑ‚." #: src/view/shell/desktop/Feeds.tsx:65 msgid "More feeds" @@ -2480,15 +2337,15 @@ msgstr "За кількіÑтю вподобань" #: src/view/com/auth/create/Step2.tsx:122 msgid "Must be at least 3 characters" -msgstr "" +msgstr "Має міÑтити щонайменше 3 Ñимволи" #: src/components/TagMenu/index.tsx:249 msgid "Mute" -msgstr "" +msgstr "Ігнорувати" #: src/components/TagMenu/index.web.tsx:105 msgid "Mute {truncatedTag}" -msgstr "" +msgstr "Ігнорувати {truncatedTag}" #: src/view/com/profile/ProfileHeader.tsx:327 msgid "Mute Account" @@ -2500,7 +2357,7 @@ msgstr "Ігнорувати облікові запиÑи" #: src/components/TagMenu/index.tsx:209 msgid "Mute all {displayTag} posts" -msgstr "" +msgstr "Ігнорувати вÑÑ– поÑти {displayTag}" #: src/components/TagMenu/index.tsx:211 #~ msgid "Mute all {tag} posts" @@ -2508,11 +2365,11 @@ msgstr "" #: src/components/dialogs/MutedWords.tsx:149 msgid "Mute in tags only" -msgstr "" +msgstr "Ігнорувати лише в тегах" #: src/components/dialogs/MutedWords.tsx:134 msgid "Mute in text & tags" -msgstr "" +msgstr "Ігнорувати в текÑті та тегах" #: src/view/screens/ProfileList.tsx:491 msgid "Mute list" @@ -2524,29 +2381,29 @@ msgstr "Ігнорувати ці облікові запиÑи?" #: src/view/screens/ProfileList.tsx:279 msgid "Mute this List" -msgstr "" +msgstr "Ігнорувати цей ÑпиÑок" #: src/components/dialogs/MutedWords.tsx:127 msgid "Mute this word in post text and tags" -msgstr "" +msgstr "Ігнорувати це Ñлово у поÑтах Ñ– тегах" #: src/components/dialogs/MutedWords.tsx:142 msgid "Mute this word in tags only" -msgstr "" +msgstr "Ігнорувати це Ñлово лише у тегах" #: src/view/com/util/forms/PostDropdownBtn.tsx:251 #: src/view/com/util/forms/PostDropdownBtn.tsx:257 msgid "Mute thread" -msgstr "Ігнорувати поÑÑ‚" +msgstr "Ігнорувати обговореннÑ" #: src/view/com/util/forms/PostDropdownBtn.tsx:267 #: src/view/com/util/forms/PostDropdownBtn.tsx:269 msgid "Mute words & tags" -msgstr "" +msgstr "Ігнорувати Ñлова та теги" #: src/view/com/lists/ListCard.tsx:102 msgid "Muted" -msgstr "" +msgstr "ІгноруєтьÑÑ" #: src/view/screens/Moderation.tsx:128 msgid "Muted accounts" @@ -2563,16 +2420,12 @@ msgstr "Ігноровані облікові запиÑи автоматичнР#: src/view/screens/Moderation.tsx:100 msgid "Muted words & tags" -msgstr "" +msgstr "Ігноровані Ñлова та теги" #: src/view/screens/ProfileList.tsx:277 msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." msgstr "Ð†Ð³Ð½Ð¾Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñ” приватним. Ігноровані кориÑтувачі можуть взаємодіÑти з вами, але ви не бачитимете Ñ—Ñ… поÑти Ñ– не отримуватимете від них Ñповіщень." -#: src/view/screens/Moderation.tsx:134 -#~ msgid "My Account" -#~ msgstr "" - #: src/view/com/modals/BirthDateSettings.tsx:56 msgid "My Birthday" msgstr "Мій день народженнÑ" @@ -2587,11 +2440,11 @@ msgstr "Мій профіль" #: src/view/screens/Settings/index.tsx:582 msgid "My Saved Feeds" -msgstr "Мої збережені Ñтрічки" +msgstr "Мої збережені канали" #: src/view/com/auth/server-input/index.tsx:118 msgid "my-server.com" -msgstr "" +msgstr "my-server.com" #: src/view/com/modals/AddAppPasswords.tsx:179 #: src/view/com/modals/CreateOrEditList.tsx:290 @@ -2600,11 +2453,11 @@ msgstr "Ім'Ñ" #: src/view/com/modals/CreateOrEditList.tsx:145 msgid "Name is required" -msgstr "" +msgstr "Ðеобхідна назва" #: src/screens/Onboarding/index.tsx:25 msgid "Nature" -msgstr "" +msgstr "Природа" #: src/view/com/auth/login/ForgotPasswordForm.tsx:190 #: src/view/com/auth/login/ForgotPasswordForm.tsx:219 @@ -2612,11 +2465,11 @@ msgstr "" #: src/view/com/auth/login/SetNewPasswordForm.tsx:196 #: src/view/com/modals/ChangePassword.tsx:166 msgid "Navigates to the next screen" -msgstr "" +msgstr "Переходить до наÑтупного екрана" #: src/view/shell/Drawer.tsx:71 msgid "Navigates to your profile" -msgstr "" +msgstr "Переходить до вашого профілю" #: src/view/com/modals/EmbedConsent.tsx:107 #: src/view/com/modals/EmbedConsent.tsx:123 @@ -2630,16 +2483,16 @@ msgstr "Ðіколи не втрачайте доÑтуп до ваших дан #: src/screens/Onboarding/StepFinished.tsx:119 msgid "Never lose access to your followers or data." -msgstr "" +msgstr "Ðіколи не втрачайте доÑтуп до ваших підпиÑників та даних." #: src/components/dialogs/MutedWords.tsx:293 msgid "Nevermind" -msgstr "" +msgstr "СкаÑувати" #: src/view/screens/Lists.tsx:76 msgctxt "action" msgid "New" -msgstr "" +msgstr "Ðовий" #: src/view/screens/ModerationModlists.tsx:78 msgid "New" @@ -2647,20 +2500,20 @@ msgstr "Ðовий" #: src/view/com/modals/CreateOrEditList.tsx:252 msgid "New Moderation List" -msgstr "" +msgstr "Ðовий ÑпиÑок модерації" #: src/view/com/auth/login/SetNewPasswordForm.tsx:150 msgid "New password" -msgstr "" +msgstr "Ðовий пароль" #: src/view/com/modals/ChangePassword.tsx:215 msgid "New Password" -msgstr "" +msgstr "Ðовий Пароль" #: src/view/com/feeds/FeedPage.tsx:126 msgctxt "action" msgid "New post" -msgstr "" +msgstr "Ðовий поÑÑ‚" #: src/view/screens/Feeds.tsx:555 #: src/view/screens/Notifications.tsx:168 @@ -2675,15 +2528,11 @@ msgstr "Ðовий поÑÑ‚" #: src/view/shell/desktop/LeftNav.tsx:258 msgctxt "action" msgid "New Post" -msgstr "" - -#: src/view/shell/desktop/LeftNav.tsx:258 -#~ msgid "New Post" -#~ msgstr "Ðовий поÑÑ‚" +msgstr "Ðовий поÑÑ‚" #: src/view/com/modals/CreateOrEditList.tsx:247 msgid "New User List" -msgstr "" +msgstr "Ðовий ÑпиÑок кориÑтувачів" #: src/view/screens/PreferencesThreads.tsx:79 msgid "Newest replies first" @@ -2691,7 +2540,7 @@ msgstr "Спочатку найновіші" #: src/screens/Onboarding/index.tsx:23 msgid "News" -msgstr "" +msgstr "Ðовини" #: src/view/com/auth/create/CreateAccount.tsx:172 #: src/view/com/auth/login/ForgotPasswordForm.tsx:182 @@ -2708,7 +2557,7 @@ msgstr "Далі" #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103 msgctxt "action" msgid "Next" -msgstr "" +msgstr "Далі" #: src/view/com/lightbox/Lightbox.web.tsx:149 msgid "Next image" @@ -2730,11 +2579,11 @@ msgstr "ÐžÐ¿Ð¸Ñ Ð²Ñ–Ð´Ñутній" #: src/view/com/profile/ProfileHeader.tsx:170 msgid "No longer following {0}" -msgstr "" +msgstr "Ви більше не підпиÑані на {0}" #: src/view/com/notifications/Feed.tsx:109 msgid "No notifications yet!" -msgstr "" +msgstr "Ще ніÑких Ñповіщень!" #: src/view/com/composer/text-input/mobile/Autocomplete.tsx:97 #: src/view/com/composer/text-input/web/Autocomplete.tsx:191 @@ -2743,17 +2592,12 @@ msgstr "Результати відÑутні" #: src/components/Lists.tsx:192 msgid "No results found" -msgstr "" +msgstr "Ðічого не знайдено" #: src/view/screens/Feeds.tsx:495 msgid "No results found for \"{query}\"" msgstr "Ðічого не знайдено за запитом «{query}»" -#: src/view/com/modals/ListAddUser.tsx:142 -#: src/view/shell/desktop/Search.tsx:112 -#~ msgid "No results found for {0}" -#~ msgstr "" - #: src/view/com/modals/ListAddRemoveUsers.tsx:127 #: src/view/screens/Search/Search.tsx:281 #: src/view/screens/Search/Search.tsx:309 @@ -2768,10 +2612,6 @@ msgstr "ÐÑ–, дÑкую" msgid "Nobody" msgstr "Ðіхто" -#: src/view/com/modals/SelfLabel.tsx:136 -#~ msgid "Not Applicable" -#~ msgstr "" - #: src/view/com/modals/SelfLabel.tsx:135 msgid "Not Applicable." msgstr "Ðе заÑтоÑовно." @@ -2779,25 +2619,17 @@ msgstr "Ðе заÑтоÑовно." #: src/Navigation.tsx:107 #: src/view/screens/Profile.tsx:106 msgid "Not Found" -msgstr "" +msgstr "Ðе знайдено" #: src/view/com/modals/VerifyEmail.tsx:246 #: src/view/com/modals/VerifyEmail.tsx:252 msgid "Not right now" -msgstr "" - -#: src/view/screens/Moderation.tsx:227 -#~ msgid "Note: Bluesky is an open and public network, and enabling this will not make your profile private or limit the ability of logged in users to see your posts. This setting only limits the visibility of posts on the Bluesky app and website; third-party apps that display Bluesky content may not respect this setting, and could show your content to logged-out users." -#~ msgstr "" +msgstr "Пізніше" #: src/view/screens/Moderation.tsx:252 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/view/screens/Moderation.tsx:227 -#~ msgid "Note: Third-party apps that display Bluesky content may not respect this setting." -#~ msgstr "" - #: src/Navigation.tsx:457 #: src/view/screens/Notifications.tsx:124 #: src/view/screens/Notifications.tsx:148 @@ -2810,7 +2642,7 @@ msgstr "СповіщеннÑ" #: src/view/com/modals/SelfLabel.tsx:103 msgid "Nudity" -msgstr "" +msgstr "ОголеніÑть" #: src/view/com/util/ErrorBoundary.tsx:35 msgid "Oh no!" @@ -2818,7 +2650,7 @@ msgstr "О, ні!" #: src/screens/Onboarding/StepInterests/index.tsx:128 msgid "Oh no! Something went wrong." -msgstr "" +msgstr "Ой! ЩоÑÑŒ пішло не так." #: src/view/com/auth/login/PasswordUpdatedForm.tsx:41 msgid "Okay" @@ -2830,11 +2662,11 @@ msgstr "Спочатку найдавніші" #: src/view/screens/Settings/index.tsx:234 msgid "Onboarding reset" -msgstr "" +msgstr "Скинути ознайомленнÑ" #: src/view/com/composer/Composer.tsx:382 msgid "One or more images is missing alt text." -msgstr "Ð”Ð»Ñ Ð¾Ð´Ð½Ð¾Ð³Ð¾ або кількох зображень відÑутній альтернативний текÑÑ‚." +msgstr "Ð”Ð»Ñ Ð¾Ð´Ð½Ð¾Ð³Ð¾ або кількох зображень відÑутній опиÑ." #: src/view/com/threadgate/WhoCanReply.tsx:100 msgid "Only {0} can reply." @@ -2842,21 +2674,21 @@ msgstr "Тільки {0} можуть відповідати." #: src/components/Lists.tsx:82 msgid "Oops, something went wrong!" -msgstr "" +msgstr "Ой, щоÑÑŒ пішло не так!" #: src/components/Lists.tsx:188 #: src/view/screens/AppPasswords.tsx:65 #: src/view/screens/Profile.tsx:106 msgid "Oops!" -msgstr "" +msgstr "Ой!" #: src/screens/Onboarding/StepFinished.tsx:115 msgid "Open" -msgstr "" +msgstr "Відкрити" #: src/view/screens/Moderation.tsx:75 msgid "Open content filtering settings" -msgstr "" +msgstr "Відкрити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ„Ñ–Ð»ÑŒÑ‚Ñ€Ð°Ñ†Ñ–Ñ— контенту" #: src/view/com/composer/Composer.tsx:477 #: src/view/com/composer/Composer.tsx:478 @@ -2865,11 +2697,11 @@ msgstr "Емоджі" #: src/view/screens/Settings/index.tsx:712 msgid "Open links with in-app browser" -msgstr "" +msgstr "Вбудований браузер" #: src/view/screens/Moderation.tsx:92 msgid "Open muted words settings" -msgstr "" +msgstr "Відкрити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ð³Ð½Ð¾Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñлів" #: src/view/com/home/HomeHeaderLayoutMobile.tsx:50 msgid "Open navigation" @@ -2877,31 +2709,31 @@ msgstr "Відкрити навігацію" #: src/view/com/util/forms/PostDropdownBtn.tsx:175 msgid "Open post options menu" -msgstr "" +msgstr "Відкрити меню налаштувань поÑту" #: src/view/screens/Settings/index.tsx:804 msgid "Open storybook page" -msgstr "" +msgstr "Відкрити storybook Ñторінку" #: src/view/com/util/forms/DropdownButton.tsx:154 msgid "Opens {numItems} options" -msgstr "" +msgstr "Відкриває меню з {numItems} опціÑми" #: src/view/screens/Log.tsx:54 msgid "Opens additional details for a debug entry" -msgstr "" +msgstr "Відкриває додаткову інформацію про Ð·Ð°Ð¿Ð¸Ñ Ð´Ð»Ñ Ð½Ð°Ð»Ð°Ð³Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ" #: src/view/com/notifications/FeedItem.tsx:349 msgid "Opens an expanded list of users in this notification" -msgstr "" +msgstr "Відкрити розширений ÑпиÑок кориÑтувачів у цьому Ñповіщенні" #: src/view/com/composer/photos/OpenCameraBtn.tsx:61 msgid "Opens camera on device" -msgstr "" +msgstr "Відкриває камеру на приÑтрої" #: src/view/com/composer/Prompt.tsx:25 msgid "Opens composer" -msgstr "" +msgstr "Відкрити редактор" #: src/view/screens/Settings/index.tsx:595 msgid "Opens configurable language settings" @@ -2909,11 +2741,11 @@ msgstr "Відкриває Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ð¾Ð²" #: src/view/com/composer/photos/SelectPhotoBtn.tsx:44 msgid "Opens device photo gallery" -msgstr "" +msgstr "Відкриває фотогалерею приÑтрою" #: src/view/com/profile/ProfileHeader.tsx:420 msgid "Opens editor for profile display name, avatar, background image, and description" -msgstr "" +msgstr "Відкриває редактор Ð´Ð»Ñ Ð½Ð°Ð·Ð²Ð¸ профілю, аватара, фонового Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ‚Ð° опиÑу" #: src/view/screens/Settings/index.tsx:649 msgid "Opens external embeds settings" @@ -2921,11 +2753,11 @@ msgstr "Відкриває Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ–Ñ… Ð²Ð±ÑƒÐ´Ñ #: src/view/com/profile/ProfileHeader.tsx:575 msgid "Opens followers list" -msgstr "" +msgstr "Відкриває ÑпиÑок підпиÑників" #: src/view/com/profile/ProfileHeader.tsx:594 msgid "Opens following list" -msgstr "" +msgstr "Відкриває ÑпиÑок нижче" #: src/view/screens/Settings.tsx:412 #~ msgid "Opens invite code list" @@ -2937,7 +2769,7 @@ msgstr "Відкриває ÑпиÑок кодів запрошеннÑ" #: src/view/screens/Settings/index.tsx:774 msgid "Opens modal for account deletion confirmation. Requires email code." -msgstr "" +msgstr "ВідкриєтьÑÑ Ð¼Ð¾Ð´Ð°Ð»ÑŒÐ½Ðµ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу. Потрібен код електронної пошти." #: src/view/com/modals/ChangeHandle.tsx:281 msgid "Opens modal for using custom domain" @@ -2949,16 +2781,16 @@ msgstr "Відкриває Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ð¾Ð´ÐµÑ€Ð°Ñ†Ñ–Ñ—" #: src/view/com/auth/login/LoginForm.tsx:239 msgid "Opens password reset form" -msgstr "" +msgstr "Відкриває форму ÑÐºÐ¸Ð´Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ" #: src/view/com/home/HomeHeaderLayout.web.tsx:63 #: src/view/screens/Feeds.tsx:356 msgid "Opens screen to edit Saved Feeds" -msgstr "" +msgstr "Відкриває Ñторінку з уÑіма збереженими Ñтрічками" #: src/view/screens/Settings/index.tsx:576 msgid "Opens screen with all saved feeds" -msgstr "Відкриває Ñторінку з уÑіма збереженими Ñтрічками" +msgstr "Відкриває Ñторінку з уÑіма збереженими каналами" #: src/view/screens/Settings/index.tsx:676 msgid "Opens the app password settings page" @@ -2966,7 +2798,7 @@ msgstr "Відкриває Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ–Ð² Ð´Ð»Ñ Ð·Ð°Ñ #: src/view/screens/Settings/index.tsx:535 msgid "Opens the home feed preferences" -msgstr "Відкриває Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾Ð¼Ð°ÑˆÐ½ÑŒÐ¾Ñ— Ñтрічки" +msgstr "Відкриває Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð“Ð¾Ð»Ð¾Ð²Ð½Ð¾Ð³Ð¾ каналу" #: src/view/screens/Settings/index.tsx:805 msgid "Opens the storybook page" @@ -2982,23 +2814,19 @@ msgstr "Відкриває Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð³Ñ–Ð»Ð¾Ðº" #: src/view/com/util/forms/DropdownButton.tsx:280 msgid "Option {0} of {numItems}" -msgstr "" +msgstr "ÐžÐ¿Ñ†Ñ–Ñ {0} з {numItems}" #: src/view/com/modals/Threadgate.tsx:89 msgid "Or combine these options:" msgstr "Ðбо ÑкіÑÑŒ із наÑтупних варіантів:" -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:122 -#~ msgid "Or you can try our \"Discover\" algorithm:" -#~ msgstr "" - #: src/view/com/auth/login/ChooseAccountForm.tsx:138 msgid "Other account" msgstr "Інший обліковий запиÑ" #: src/view/com/modals/ServerInput.tsx:88 #~ msgid "Other service" -#~ msgstr "Інший хоÑтинг-провайдер" +#~ msgstr "" #: src/view/com/composer/select-language/SelectLangBtn.tsx:91 msgid "Other..." @@ -3011,7 +2839,7 @@ msgstr "Сторінку не знайдено" #: src/view/screens/NotFound.tsx:42 msgid "Page Not Found" -msgstr "" +msgstr "Сторінку не знайдено" #: src/view/com/auth/create/Step1.tsx:191 #: src/view/com/auth/create/Step1.tsx:201 @@ -3031,23 +2859,23 @@ msgstr "Пароль змінено!" #: src/Navigation.tsx:162 msgid "People followed by @{0}" -msgstr "" +msgstr "Люди, на Ñких підпиÑаний(-на) @{0}" #: src/Navigation.tsx:155 msgid "People following @{0}" -msgstr "" +msgstr "Люди, Ñкі підпиÑані на @{0}" #: src/view/com/lightbox/Lightbox.tsx:66 msgid "Permission to access camera roll is required." -msgstr "" +msgstr "Потрібен дозвіл на доÑтуп до камери." #: src/view/com/lightbox/Lightbox.tsx:72 msgid "Permission to access camera roll was denied. Please enable it in your system settings." -msgstr "" +msgstr "Дозвіл на доÑтуп до камери був заборонений. Будь лаÑка, включіть його в налаштуваннÑÑ… ÑиÑтеми." #: src/screens/Onboarding/index.tsx:31 msgid "Pets" -msgstr "" +msgstr "Домашні улюбленці" #: src/view/com/auth/create/Step2.tsx:183 #~ msgid "Phone number" @@ -3060,7 +2888,7 @@ msgstr "ЗображеннÑ, призначені Ð´Ð»Ñ Ð´Ð¾Ñ€Ð¾Ñлих." #: src/view/screens/ProfileFeed.tsx:354 #: src/view/screens/ProfileList.tsx:581 msgid "Pin to home" -msgstr "" +msgstr "Закріпити" #: src/view/screens/SavedFeeds.tsx:88 msgid "Pinned Feeds" @@ -3089,7 +2917,7 @@ msgstr "Будь лаÑка, оберіть ваш пароль." #: src/view/com/auth/create/state.ts:131 msgid "Please complete the verification captcha." -msgstr "" +msgstr "Будь лаÑка, завершіть перевірку Captcha." #: src/view/com/modals/ChangeEmail.tsx:67 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." @@ -3097,7 +2925,7 @@ msgstr "Будь лаÑка, підтвердіть вашу ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ñ #: src/view/com/modals/AddAppPasswords.tsx:90 msgid "Please enter a name for your app password. All spaces is not allowed." -msgstr "" +msgstr "Будь лаÑка, введіть ім'Ñ Ð´Ð»Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð·Ð°ÑтоÑунку. Пробіли Ñ– пропуÑки не допуÑкаютьÑÑ." #: src/view/com/auth/create/Step2.tsx:206 #~ msgid "Please enter a phone number that can receive SMS text messages." @@ -3109,7 +2937,7 @@ msgstr "Будь лаÑка, введіть унікальну назву Ð´Ð»Ñ #: src/components/dialogs/MutedWords.tsx:68 msgid "Please enter a valid word, tag, or phrase to mute" -msgstr "" +msgstr "Будь лаÑка, введіть допуÑтиме Ñлово, тег або фразу Ð´Ð»Ñ Ñ–Ð³Ð½Ð¾Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ" #: src/view/com/auth/create/state.ts:170 #~ msgid "Please enter the code you received by SMS." @@ -3139,7 +2967,7 @@ msgstr "Будь лаÑка, вкажіть чому ви вважаєте що #: src/view/com/modals/VerifyEmail.tsx:101 msgid "Please Verify Your Email" -msgstr "" +msgstr "Підтвердьте Ñвою адреÑу електронної пошти" #: src/view/com/composer/Composer.tsx:222 msgid "Please wait for your link card to finish loading" @@ -3147,42 +2975,36 @@ msgstr "Будь лаÑка, зачекайте доки завершитьÑÑ #: src/screens/Onboarding/index.tsx:37 msgid "Politics" -msgstr "" +msgstr "Політика" #: src/view/com/modals/SelfLabel.tsx:111 msgid "Porn" -msgstr "" +msgstr "ПорнографіÑ" #: src/view/com/composer/Composer.tsx:357 #: src/view/com/composer/Composer.tsx:365 msgctxt "action" msgid "Post" -msgstr "" +msgstr "ЗапоÑтити" #: src/view/com/post-thread/PostThread.tsx:303 msgctxt "description" msgid "Post" -msgstr "" - -#: src/view/com/composer/Composer.tsx:346 -#: src/view/com/post-thread/PostThread.tsx:225 -#: src/view/screens/PostThread.tsx:80 -#~ msgid "Post" -#~ msgstr "ПоÑÑ‚" +msgstr "ПоÑÑ‚" #: src/view/com/post-thread/PostThreadItem.tsx:175 msgid "Post by {0}" -msgstr "" +msgstr "ПоÑÑ‚ від {0}" #: src/Navigation.tsx:174 #: src/Navigation.tsx:181 #: src/Navigation.tsx:188 msgid "Post by @{0}" -msgstr "" +msgstr "ПоÑÑ‚ від @{0}" #: src/view/com/util/forms/PostDropdownBtn.tsx:108 msgid "Post deleted" -msgstr "" +msgstr "ПоÑÑ‚ видалено" #: src/view/com/post-thread/PostThread.tsx:462 msgid "Post hidden" @@ -3202,7 +3024,7 @@ msgstr "ПоÑÑ‚ не знайдено" #: src/components/TagMenu/index.tsx:253 msgid "posts" -msgstr "" +msgstr "поÑти" #: src/view/screens/Profile.tsx:180 msgid "Posts" @@ -3210,11 +3032,11 @@ msgstr "ПоÑти" #: src/components/dialogs/MutedWords.tsx:90 msgid "Posts can be muted based on their text, their tags, or both." -msgstr "" +msgstr "ПоÑти можуть бути ігноровані за їхнім текÑтом, тегами чи за обома." #: src/view/com/posts/FeedErrorMessage.tsx:64 msgid "Posts hidden" -msgstr "" +msgstr "ПоÑти приховано" #: src/view/com/modals/LinkWarning.tsx:46 msgid "Potentially Misleading Link" @@ -3258,7 +3080,7 @@ msgstr "Профіль" #: src/view/com/modals/EditProfile.tsx:128 msgid "Profile updated" -msgstr "" +msgstr "Профіль оновлено" #: src/view/screens/Settings/index.tsx:949 msgid "Protect your account by verifying your email." @@ -3266,7 +3088,7 @@ msgstr "ЗахиÑтіть Ñвій обліковий запиÑ, Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ #: src/screens/Onboarding/StepFinished.tsx:101 msgid "Public" -msgstr "" +msgstr "Публічний" #: src/view/screens/ModerationModlists.tsx:61 msgid "Public, shareable lists of users to mute or block in bulk." @@ -3278,16 +3100,16 @@ msgstr "Публічні, поширювані ÑпиÑки Ð´Ð»Ñ ÑтвореР#: src/view/com/composer/Composer.tsx:342 msgid "Publish post" -msgstr "" +msgstr "Опублікувати поÑÑ‚" #: src/view/com/composer/Composer.tsx:342 msgid "Publish reply" -msgstr "" +msgstr "Опублікувати відповідь" #: src/view/com/modals/Repost.tsx:65 msgctxt "action" msgid "Quote post" -msgstr "" +msgstr "Цитувати" #: src/view/com/util/post-ctrls/RepostButton.web.tsx:58 msgid "Quote post" @@ -3296,11 +3118,7 @@ msgstr "Цитувати поÑÑ‚" #: src/view/com/modals/Repost.tsx:70 msgctxt "action" msgid "Quote Post" -msgstr "" - -#: src/view/com/modals/Repost.tsx:56 -#~ msgid "Quote Post" -#~ msgstr "Цитувати поÑÑ‚" +msgstr "Цитувати" #: src/view/screens/PreferencesThreads.tsx:86 msgid "Random (aka \"Poster's Roulette\")" @@ -3310,11 +3128,6 @@ msgstr "У випадковому порÑдку" msgid "Ratios" msgstr "Ð¡Ð¿Ñ–Ð²Ð²Ñ–Ð´Ð½Ð¾ÑˆÐµÐ½Ð½Ñ Ñторін" -#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:73 -#: src/view/com/auth/onboarding/RecommendedFollows.tsx:50 -#~ msgid "Recommended" -#~ msgstr "" - #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:116 msgid "Recommended Feeds" msgstr "Рекомендовані Ñтрічки" @@ -3330,7 +3143,7 @@ msgstr "Рекомендовані кориÑтувачі" #: src/view/com/util/UserAvatar.tsx:285 #: src/view/com/util/UserBanner.tsx:91 msgid "Remove" -msgstr "Вилучити" +msgstr "Видалити" #: src/view/com/feeds/FeedSourceCard.tsx:108 msgid "Remove {0} from my feeds?" @@ -3338,12 +3151,12 @@ msgstr "Вилучити {0} зі збережених Ñтрічок?" #: src/view/com/util/AccountDropdownBtn.tsx:22 msgid "Remove account" -msgstr "Вилучити обліковий запиÑ" +msgstr "Видалити обліковий запиÑ" #: src/view/com/posts/FeedErrorMessage.tsx:131 #: src/view/com/posts/FeedErrorMessage.tsx:166 msgid "Remove feed" -msgstr "Вилучити Ñтрічку" +msgstr "Видалити Ñтрічку" #: src/view/com/feeds/FeedSourceCard.tsx:107 #: src/view/com/feeds/FeedSourceCard.tsx:169 @@ -3363,11 +3176,11 @@ msgstr "Вилучити попередній переглÑд зображенР#: src/components/dialogs/MutedWords.tsx:343 msgid "Remove mute word from your list" -msgstr "" +msgstr "Вилучити ігноровані Ñлова з вашого ÑпиÑку" #: src/view/com/modals/Repost.tsx:47 msgid "Remove repost" -msgstr "" +msgstr "Видалити репоÑÑ‚" #: src/view/com/feeds/FeedSourceCard.tsx:175 msgid "Remove this feed from my feeds?" @@ -3385,11 +3198,11 @@ msgstr "Вилучено зі ÑпиÑку" #: src/view/com/feeds/FeedSourceCard.tsx:113 #: src/view/com/feeds/FeedSourceCard.tsx:180 msgid "Removed from my feeds" -msgstr "" +msgstr "Вилучено з моїх Ñтрічок" #: src/view/com/composer/ExternalEmbed.tsx:71 msgid "Removes default thumbnail from {0}" -msgstr "" +msgstr "ВидалÑÑ” мініатюру за замовчуваннÑм з {0}" #: src/view/screens/Profile.tsx:181 msgid "Replies" @@ -3402,7 +3215,7 @@ msgstr "Відповіді до цього поÑту вимкнено" #: src/view/com/composer/Composer.tsx:355 msgctxt "action" msgid "Reply" -msgstr "" +msgstr "ВідповіÑти" #: src/view/screens/PreferencesFollowingFeed.tsx:144 msgid "Reply Filters" @@ -3412,7 +3225,7 @@ msgstr "Які відповіді показувати" #: src/view/com/posts/FeedItem.tsx:287 msgctxt "description" msgid "Reply to <0/>" -msgstr "" +msgstr "У відповідь <0/>" #: src/view/com/modals/report/Modal.tsx:166 msgid "Report {collectionName}" @@ -3442,7 +3255,7 @@ msgstr "ПоÑкаржитиÑÑŒ на поÑÑ‚" #: src/view/com/util/post-ctrls/RepostButton.tsx:61 msgctxt "action" msgid "Repost" -msgstr "" +msgstr "РепоÑÑ‚" #: src/view/com/util/post-ctrls/RepostButton.web.tsx:48 msgid "Repost" @@ -3454,32 +3267,24 @@ msgid "Repost or quote post" msgstr "РепоÑтити або цитувати" #: src/view/screens/PostRepostedBy.tsx:27 -#~ msgid "Reposted by" -#~ msgstr "РепоÑÑ‚" - -#: src/view/screens/PostRepostedBy.tsx:27 msgid "Reposted By" -msgstr "" +msgstr "Зробив(-ла) репоÑÑ‚" #: src/view/com/posts/FeedItem.tsx:207 msgid "Reposted by {0}" -msgstr "" - -#: src/view/com/posts/FeedItem.tsx:206 -#~ msgid "Reposted by {0})" -#~ msgstr "" +msgstr "{0} зробив(-ла) репоÑÑ‚" #: src/view/com/posts/FeedItem.tsx:224 msgid "Reposted by <0/>" -msgstr "" +msgstr "<0/> зробив(-ла) репоÑÑ‚" #: src/view/com/notifications/FeedItem.tsx:162 msgid "reposted your post" -msgstr "" +msgstr "зробив(-ла) репоÑÑ‚ вашого допиÑу" #: src/view/com/post-thread/PostThreadItem.tsx:188 msgid "Reposts of this post" -msgstr "" +msgstr "РепоÑти цього поÑта" #: src/view/com/modals/ChangeEmail.tsx:181 #: src/view/com/modals/ChangeEmail.tsx:183 @@ -3493,15 +3298,11 @@ msgstr "Змінити" #: src/view/com/modals/ChangePassword.tsx:239 #: src/view/com/modals/ChangePassword.tsx:241 msgid "Request Code" -msgstr "" - -#: src/view/screens/Moderation.tsx:188 -#~ msgid "Request to limit the visibility of my account" -#~ msgstr "" +msgstr "ÐадіÑлати запит на код" #: src/view/screens/Settings/index.tsx:456 msgid "Require alt text before posting" -msgstr "Вимагати альтернативний текÑÑ‚ до зображень перед публікацією" +msgstr "Вимагати Ð¾Ð¿Ð¸Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½ÑŒ перед публікацією" #: src/view/com/auth/create/Step1.tsx:146 msgid "Required for this provider" @@ -3514,11 +3315,11 @@ msgstr "Код підтвердженнÑ" #: src/view/com/modals/ChangePassword.tsx:190 msgid "Reset Code" -msgstr "" +msgstr "Код ÑкиданнÑ" #: src/view/screens/Settings/index.tsx:824 msgid "Reset onboarding" -msgstr "" +msgstr "Скинути ознайомленнÑ" #: src/view/screens/Settings/index.tsx:827 msgid "Reset onboarding state" @@ -3530,7 +3331,7 @@ msgstr "Скинути пароль" #: src/view/screens/Settings/index.tsx:814 msgid "Reset preferences" -msgstr "" +msgstr "Скинути налаштуваннÑ" #: src/view/screens/Settings/index.tsx:817 msgid "Reset preferences state" @@ -3546,12 +3347,12 @@ msgstr "" #: src/view/com/auth/login/LoginForm.tsx:269 msgid "Retries login" -msgstr "" +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 "" +msgstr "Повторити оÑтанню дію, Ñка Ñпричинила помилку" #: src/screens/Onboarding/StepInterests/index.tsx:221 #: src/screens/Onboarding/StepInterests/index.tsx:224 @@ -3564,17 +3365,13 @@ msgstr "" msgid "Retry" msgstr "Повторити Ñпробу" -#: src/view/com/modals/ChangeHandle.tsx:169 -#~ msgid "Retry change handle" -#~ msgstr "" - #: src/view/com/auth/create/Step2.tsx:247 #~ msgid "Retry." #~ msgstr "" #: src/view/screens/ProfileList.tsx:903 msgid "Return to previous page" -msgstr "" +msgstr "ПовернутиÑÑ Ð´Ð¾ попередньої Ñторінки" #: src/view/shell/desktop/RightNav.tsx:55 #~ msgid "SANDBOX. Posts and accounts are not permanent." @@ -3584,7 +3381,7 @@ msgstr "" #: src/view/com/modals/CreateOrEditList.tsx:345 msgctxt "action" msgid "Save" -msgstr "" +msgstr "Зберегти" #: src/view/com/modals/BirthDateSettings.tsx:94 #: src/view/com/modals/BirthDateSettings.tsx:97 @@ -3597,11 +3394,7 @@ msgstr "Зберегти" #: src/view/com/modals/AltImage.tsx:130 msgid "Save alt text" -msgstr "Зберегти альтернативний текÑÑ‚" - -#: src/view/com/modals/UserAddRemoveLists.tsx:212 -#~ msgid "Save changes" -#~ msgstr "" +msgstr "Зберегти опиÑ" #: src/view/com/modals/EditProfile.tsx:232 msgid "Save Changes" @@ -3621,19 +3414,19 @@ msgstr "Збережені Ñтрічки" #: src/view/com/modals/EditProfile.tsx:225 msgid "Saves any changes to your profile" -msgstr "" +msgstr "Зберігає зміни вашого профілю" #: src/view/com/modals/ChangeHandle.tsx:171 msgid "Saves handle change to {handle}" -msgstr "" +msgstr "Зберігає зміню пÑевдоніму на {handle}" #: src/screens/Onboarding/index.tsx:36 msgid "Science" -msgstr "" +msgstr "Ðаука" #: src/view/screens/ProfileList.tsx:859 msgid "Scroll to top" -msgstr "" +msgstr "Прогорнути вгору" #: src/Navigation.tsx:447 #: src/view/com/auth/LoggedOut.tsx:122 @@ -3655,11 +3448,11 @@ msgstr "Пошук" #: src/view/screens/Search/Search.tsx:735 #: src/view/shell/desktop/Search.tsx:255 msgid "Search for \"{query}\"" -msgstr "" +msgstr "Шукати \"{query}\"" #: src/components/TagMenu/index.tsx:145 msgid "Search for all posts by @{authorHandle} with tag {displayTag}" -msgstr "" +msgstr "Пошук уÑÑ–Ñ… повідомлень @{authorHandle} з тегом {displayTag}" #: src/components/TagMenu/index.tsx:145 #~ msgid "Search for all posts by @{authorHandle} with tag {tag}" @@ -3667,16 +3460,12 @@ msgstr "" #: src/components/TagMenu/index.tsx:94 msgid "Search for all posts with tag {displayTag}" -msgstr "" +msgstr "Пошук уÑÑ–Ñ… повідомлень з тегом {displayTag}" #: src/components/TagMenu/index.tsx:90 #~ msgid "Search for all posts with tag {tag}" #~ msgstr "" -#: src/view/screens/Search/Search.tsx:390 -#~ msgid "Search for posts and users." -#~ msgstr "" - #: src/view/com/auth/LoggedOut.tsx:104 #: src/view/com/auth/LoggedOut.tsx:105 #: src/view/com/modals/ListAddRemoveUsers.tsx:70 @@ -3689,19 +3478,19 @@ msgstr "Потрібен код підтвердженнÑ" #: src/components/TagMenu/index.web.tsx:66 msgid "See {truncatedTag} posts" -msgstr "" +msgstr "ПереглÑнути допиÑи {truncatedTag}" #: src/components/TagMenu/index.web.tsx:83 msgid "See {truncatedTag} posts by user" -msgstr "" +msgstr "ПереглÑнути поÑти кориÑтувача з {truncatedTag}" #: src/components/TagMenu/index.tsx:128 msgid "See <0>{displayTag}</0> posts" -msgstr "" +msgstr "ПереглÑнути поÑти з <0>{displayTag}</0>" #: src/components/TagMenu/index.tsx:187 msgid "See <0>{displayTag}</0> posts by this user" -msgstr "" +msgstr "ПереглÑнути поÑти цього кориÑтувача з <0>{displayTag}</0>" #: src/components/TagMenu/index.tsx:128 #~ msgid "See <0>{tag}</0> posts" @@ -3713,7 +3502,7 @@ msgstr "" #: src/view/screens/SavedFeeds.tsx:163 msgid "See this guide" -msgstr "" +msgstr "ПереглÑньте цей поÑібник" #: src/view/com/auth/HomeLoggedOutCTA.tsx:39 msgid "See what's next" @@ -3721,11 +3510,11 @@ msgstr "Що далі?" #: src/view/com/util/Selector.tsx:106 msgid "Select {item}" -msgstr "" +msgstr "Обрати {item}" #: src/view/com/modals/ServerInput.tsx:75 #~ msgid "Select Bluesky Social" -#~ msgstr "Вибрати Bluesky Social" +#~ msgstr "" #: src/view/com/auth/login/Login.tsx:117 msgid "Select from an existing account" @@ -3733,7 +3522,7 @@ msgstr "Вибрати Ñ–Ñнуючий обліковий запиÑ" #: src/view/com/util/Selector.tsx:107 msgid "Select option {i} of {numItems}" -msgstr "" +msgstr "Обрати варіант {i} із {numItems}" #: src/view/com/auth/create/Step1.tsx:96 #: src/view/com/auth/login/LoginForm.tsx:150 @@ -3742,11 +3531,11 @@ msgstr "Вибрати хоÑтинг-провайдера" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:52 msgid "Select some accounts below to follow" -msgstr "" +msgstr "Оберіть деÑкі облікові запиÑи, щоб підпиÑатиÑÑ" #: src/view/com/auth/server-input/index.tsx:82 msgid "Select the service that hosts your data." -msgstr "" +msgstr "Виберіть хоÑтинг-провайдера Ð´Ð»Ñ Ð²Ð°ÑˆÐ¸Ñ… даних." #: src/screens/Onboarding/StepModeration/index.tsx:49 #~ msgid "Select the types of content that you want to see (or not see), and we'll handle the rest." @@ -3754,15 +3543,15 @@ msgstr "" #: src/screens/Onboarding/StepTopicalFeeds.tsx:96 msgid "Select topical feeds to follow from the list below" -msgstr "" +msgstr "ПідпишітьÑÑ Ð½Ð° тематичні Ñтрічки зі ÑпиÑку нижче" #: src/screens/Onboarding/StepModeration/index.tsx:75 msgid "Select what you want to see (or not see), and we’ll handle the rest." -msgstr "" +msgstr "Виберіть, що ви хочете бачити (або не бачити), а решту ми зробимо за ваÑ." #: src/view/screens/LanguageSettings.tsx:281 msgid "Select which languages you want your subscribed feeds to include. If none are selected, all languages will be shown." -msgstr "Оберіть мови поÑтів, Ñкі ви хочете бачити у збережених Ñтрічках. Якщо не вибрано жодної - буде показано поÑти вÑіма мовами." +msgstr "Оберіть мови поÑтів, Ñкі ви хочете бачити у збережених каналах. Якщо не вибрано жодної – буде показано поÑти вÑіма мовами." #: src/view/screens/LanguageSettings.tsx:98 msgid "Select your app language for the default text to display in the app" @@ -3770,7 +3559,7 @@ msgstr "Оберіть мову інтерфейÑу" #: src/screens/Onboarding/StepInterests/index.tsx:196 msgid "Select your interests from the options below" -msgstr "" +msgstr "Виберіть ваші інтереÑи із нижченаведених варіантів" #: src/view/com/auth/create/Step2.tsx:155 #~ msgid "Select your phone's country" @@ -3782,11 +3571,11 @@ msgstr "Оберіть бажану мову Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐºÐ»Ð°Ð´Ñ–Ð² у ва #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:116 msgid "Select your primary algorithmic feeds" -msgstr "" +msgstr "Оберіть ваші оÑновні алгоритмічні Ñтрічки" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:142 msgid "Select your secondary algorithmic feeds" -msgstr "" +msgstr "Оберіть ваші другорÑдні алгоритмічні Ñтрічки" #: src/view/com/modals/VerifyEmail.tsx:202 #: src/view/com/modals/VerifyEmail.tsx:204 @@ -3800,11 +3589,7 @@ msgstr "ÐадіÑлати ел. лиÑта" #: src/view/com/modals/DeleteAccount.tsx:144 msgctxt "action" msgid "Send Email" -msgstr "" - -#: src/view/com/modals/DeleteAccount.tsx:138 -#~ msgid "Send Email" -#~ msgstr "ÐадіÑлати ел. лиÑта" +msgstr "ÐадіÑлати ел. лиÑÑ‚" #: src/view/shell/Drawer.tsx:295 #: src/view/shell/Drawer.tsx:316 @@ -3817,41 +3602,41 @@ msgstr "ПоÑкаржитиÑÑŒ" #: src/view/com/modals/DeleteAccount.tsx:133 msgid "Sends email with confirmation code for account deletion" -msgstr "" +msgstr "ÐадÑилає електронний лиÑÑ‚ з кодом Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу" #: src/view/com/auth/server-input/index.tsx:110 msgid "Server address" -msgstr "" +msgstr "ÐдреÑа Ñервера" #: src/view/com/modals/ContentFilteringSettings.tsx:311 msgid "Set {value} for {labelGroup} content moderation policy" -msgstr "" +msgstr "Ð’Ñтановити {value} Ð´Ð»Ñ Ð¿Ð¾Ð»Ñ–Ñ‚Ð¸ÐºÐ¸ модерації вміÑту {labelGroup}" #: src/view/com/modals/ContentFilteringSettings.tsx:160 #: src/view/com/modals/ContentFilteringSettings.tsx:179 msgctxt "action" msgid "Set Age" -msgstr "" +msgstr "Ð’Ñтановити вік" #: src/view/screens/Settings/index.tsx:488 msgid "Set color theme to dark" -msgstr "" +msgstr "Ð’Ñтановити темне оформленнÑ" #: src/view/screens/Settings/index.tsx:481 msgid "Set color theme to light" -msgstr "" +msgstr "Ð’Ñтановити Ñвітле оформленнÑ" #: src/view/screens/Settings/index.tsx:475 msgid "Set color theme to system setting" -msgstr "" +msgstr "Ð’Ñтановити ÑиÑтемне оформленнÑ" #: src/view/screens/Settings/index.tsx:514 msgid "Set dark theme to the dark theme" -msgstr "" +msgstr "Ð’Ñтановити темну тему" #: src/view/screens/Settings/index.tsx:507 msgid "Set dark theme to the dim theme" -msgstr "" +msgstr "Ð’Ñтановити темну тьмÑну тему" #: src/view/com/auth/login/SetNewPasswordForm.tsx:104 msgid "Set new password" @@ -3859,19 +3644,19 @@ msgstr "Зміна паролÑ" #: src/view/com/auth/create/Step1.tsx:202 msgid "Set password" -msgstr "" +msgstr "Ð’Ñтановити пароль" #: src/view/screens/PreferencesFollowingFeed.tsx:225 msgid "Set this setting to \"No\" to hide all quote posts from your feed. Reposts will still be visible." -msgstr "Вимкніть це налаштуваннÑ, щоб приховати вÑÑ– цитовані поÑти у вашій Ñтрічці. Ðе впливає на репоÑти без цитуваннÑ." +msgstr "Вимкніть цей параметр, щоб приховати вÑÑ– цитовані поÑти у вашій Ñтрічці. Ðе впливає на репоÑти без цитуваннÑ." #: src/view/screens/PreferencesFollowingFeed.tsx:122 msgid "Set this setting to \"No\" to hide all replies from your feed." -msgstr "Вимкніть це налаштуваннÑ, щоб приховати вÑÑ– відповіді у вашій Ñтрічці." +msgstr "Вимкніть цей параметр, щоб приховати вÑÑ– відповіді у вашій Ñтрічці." #: src/view/screens/PreferencesFollowingFeed.tsx:191 msgid "Set this setting to \"No\" to hide all reposts from your feed." -msgstr "Вимкніть це налаштуваннÑ, щоб приховати вÑÑ– репоÑти у вашій Ñтрічці." +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." @@ -3879,36 +3664,32 @@ msgstr "Увімкніть це налаштуваннÑ, щоб показувР#: src/view/screens/PreferencesHomeFeed.tsx:261 #~ msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your following feed. This is an experimental feature." -#~ msgstr "Увімкніть це налаштуваннÑ, щоб іноді бачити поÑти зі збережених Ñтрічок у вашій домашній Ñтрічці. Це екÑпериментальна функціÑ." +#~ msgstr "" #: src/view/screens/PreferencesFollowingFeed.tsx:261 msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your Following feed. This is an experimental feature." -msgstr "" +msgstr "Увімкніть це налаштуваннÑ, щоб іноді бачити поÑти зі збережених Ñтрічок у вашій домашній Ñтрічці. Це екÑпериментальна функціÑ." #: src/screens/Onboarding/Layout.tsx:50 msgid "Set up your account" -msgstr "" +msgstr "Ðалаштуйте ваш обліковий запиÑ" #: src/view/com/modals/ChangeHandle.tsx:266 msgid "Sets Bluesky username" -msgstr "" +msgstr "Ð’Ñтановлює пÑевдонім Bluesky" #: src/view/com/auth/login/ForgotPasswordForm.tsx:157 msgid "Sets email for password reset" -msgstr "" +msgstr "Ð’Ñтановлює ел. адреÑу Ð´Ð»Ñ ÑÐºÐ¸Ð´Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ" #: src/view/com/auth/login/ForgotPasswordForm.tsx:122 msgid "Sets hosting provider for password reset" -msgstr "" - -#: src/view/com/auth/create/Step1.tsx:143 -#~ msgid "Sets hosting provider to {label}" -#~ msgstr "" +msgstr "Ð’Ñтановлює хоÑтинг-провайдером Ð´Ð»Ñ ÑÐºÐ¸Ð´Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ" #: src/view/com/auth/create/Step1.tsx:97 #: src/view/com/auth/login/LoginForm.tsx:151 msgid "Sets server for the Bluesky client" -msgstr "" +msgstr "Ð’Ñтановлює Ñервер Ð´Ð»Ñ Ð·Ð°ÑтоÑунку Bluesky" #: src/Navigation.tsx:137 #: src/view/screens/Settings/index.tsx:294 @@ -3925,7 +3706,7 @@ msgstr "СекÑуальна активніÑть або еротична ого #: src/view/com/lightbox/Lightbox.tsx:141 msgctxt "action" msgid "Share" -msgstr "" +msgstr "Поширити" #: src/view/com/profile/ProfileHeader.tsx:295 #: src/view/com/util/forms/PostDropdownBtn.tsx:231 @@ -3939,21 +3720,17 @@ msgstr "Поширити" msgid "Share feed" msgstr "Поширити Ñтрічку" -#: src/view/screens/ProfileFeed.tsx:276 -#~ msgid "Share link" -#~ msgstr "" - #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:43 #: src/view/com/modals/ContentFilteringSettings.tsx:266 #: src/view/com/util/moderation/ContentHider.tsx:107 #: src/view/com/util/moderation/PostHider.tsx:108 #: src/view/screens/Settings/index.tsx:344 msgid "Show" -msgstr "Показати" +msgstr "Показувати" #: src/view/screens/PreferencesFollowingFeed.tsx:68 msgid "Show all replies" -msgstr "" +msgstr "Показати вÑÑ– відповіді" #: src/view/com/util/moderation/ScreenHider.tsx:132 msgid "Show anyway" @@ -3965,13 +3742,13 @@ msgstr "Показати Ð²Ð±ÑƒÐ´ÑƒÐ²Ð°Ð½Ð½Ñ Ð· {0}" #: src/view/com/profile/ProfileHeader.tsx:459 msgid "Show follows similar to {0}" -msgstr "" +msgstr "Показати підпиÑки, Ñхожі на {0}" #: src/view/com/post-thread/PostThreadItem.tsx:538 #: src/view/com/post/Post.tsx:198 #: src/view/com/posts/FeedItem.tsx:363 msgid "Show More" -msgstr "" +msgstr "Показати більше" #: src/view/screens/PreferencesFollowingFeed.tsx:258 msgid "Show Posts from My Feeds" @@ -3983,15 +3760,15 @@ msgstr "Показувати цитати" #: src/screens/Onboarding/StepFollowingFeed.tsx:118 msgid "Show quote-posts in Following feed" -msgstr "" +msgstr "Показувати Ñ†Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñƒ Ñтрічці \"Following\"" #: src/screens/Onboarding/StepFollowingFeed.tsx:134 msgid "Show quotes in Following" -msgstr "" +msgstr "Показувати Ñ†Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñƒ Ñтрічці \"Following\"" #: src/screens/Onboarding/StepFollowingFeed.tsx:94 msgid "Show re-posts in Following feed" -msgstr "" +msgstr "Показувати репоÑти у Ñтрічці \"Following\"" #: src/view/screens/PreferencesFollowingFeed.tsx:119 msgid "Show Replies" @@ -4003,15 +3780,15 @@ msgstr "Показувати відповіді від людей, за Ñким #: src/screens/Onboarding/StepFollowingFeed.tsx:86 msgid "Show replies in Following" -msgstr "" +msgstr "Показувати відповіді у Ñтрічці \"Following\"" #: src/screens/Onboarding/StepFollowingFeed.tsx:70 msgid "Show replies in Following feed" -msgstr "" +msgstr "Показувати відповіді у Ñтрічці \"Following\"" #: src/view/screens/PreferencesFollowingFeed.tsx:70 msgid "Show replies with at least {value} {0}" -msgstr "" +msgstr "Показувати відповіді від {value} {0}" #: src/view/screens/PreferencesFollowingFeed.tsx:188 msgid "Show Reposts" @@ -4019,12 +3796,12 @@ msgstr "Показувати репоÑти" #: src/screens/Onboarding/StepFollowingFeed.tsx:110 msgid "Show reposts in Following" -msgstr "" +msgstr "Показувати репоÑти у Ñтрічці \"Following\"" #: src/view/com/util/moderation/ContentHider.tsx:67 #: src/view/com/util/moderation/PostHider.tsx:61 msgid "Show the content" -msgstr "" +msgstr "Показати вміÑÑ‚" #: src/view/com/notifications/FeedItem.tsx:347 msgid "Show users" @@ -4032,12 +3809,12 @@ msgstr "Показати кориÑтувачів" #: src/view/com/profile/ProfileHeader.tsx:462 msgid "Shows a list of users similar to this user." -msgstr "" +msgstr "Показує ÑпиÑок кориÑтувачів, Ñхожих на цього." #: src/view/com/post-thread/PostThreadFollowBtn.tsx:124 #: src/view/com/profile/ProfileHeader.tsx:506 msgid "Shows posts from {0} in your feed" -msgstr "" +msgstr "Показує допиÑи з {0} у вашій Ñтрічці" #: src/view/com/auth/HomeLoggedOutCTA.tsx:70 #: src/view/com/auth/login/Login.tsx:98 @@ -4105,11 +3882,11 @@ msgstr "Ви увійшли Ñк" #: src/view/com/auth/login/ChooseAccountForm.tsx:103 msgid "Signed in as @{0}" -msgstr "" +msgstr "Ви увійшли Ñк @{0}" #: src/view/com/modals/SwitchAccount.tsx:66 msgid "Signs {0} out of Bluesky" -msgstr "" +msgstr "Виходить з Bluesky облікового запиÑу {0}" #: src/screens/Onboarding/StepInterests/index.tsx:235 #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:195 @@ -4119,7 +3896,7 @@ msgstr "ПропуÑтити" #: src/screens/Onboarding/StepInterests/index.tsx:232 msgid "Skip this flow" -msgstr "" +msgstr "ПропуÑтити цей процеÑ" #: src/view/com/auth/create/Step2.tsx:82 #~ msgid "SMS verification" @@ -4127,7 +3904,7 @@ msgstr "" #: src/screens/Onboarding/index.tsx:40 msgid "Software Dev" -msgstr "" +msgstr "Розробка П/З" #: src/view/com/modals/ProfilePreview.tsx:62 #~ msgid "Something went wrong and we're not sure what." @@ -4135,7 +3912,7 @@ msgstr "" #: src/components/Lists.tsx:203 msgid "Something went wrong!" -msgstr "" +msgstr "ЩоÑÑŒ пішло не так!" #: src/view/com/modals/Waitlist.tsx:51 #~ msgid "Something went wrong. Check your email and try again." @@ -4143,7 +3920,7 @@ msgstr "" #: src/App.native.tsx:66 msgid "Sorry! Your session expired. Please log in again." -msgstr "" +msgstr "Даруйте! Ваш ÑÐµÐ°Ð½Ñ Ð²Ð¸Ñ‡ÐµÑ€Ð¿Ð°Ð²ÑÑ. Будь лаÑка, увійдіть знову." #: src/view/screens/PreferencesThreads.tsx:69 msgid "Sort Replies" @@ -4155,7 +3932,7 @@ msgstr "Оберіть, Ñк Ñортувати відповіді до поÑÑ‚ #: src/screens/Onboarding/index.tsx:30 msgid "Sports" -msgstr "" +msgstr "Спорт" #: src/view/com/modals/crop-image/CropImage.web.tsx:122 msgid "Square" @@ -4171,15 +3948,11 @@ msgstr "Сторінка Ñтану" #: src/view/com/auth/create/StepHeader.tsx:22 msgid "Step {0} of {numSteps}" -msgstr "" - -#: src/view/com/auth/create/StepHeader.tsx:15 -#~ msgid "Step {step} of 3" -#~ msgstr "" +msgstr "Крок {0} / {numSteps}" #: src/view/screens/Settings/index.tsx:274 msgid "Storage cleared, you need to restart the app now." -msgstr "" +msgstr "Сховище очищено, тепер вам треба перезапуÑтити заÑтоÑунок." #: src/Navigation.tsx:204 #: src/view/screens/Settings/index.tsx:807 @@ -4197,27 +3970,23 @@ msgstr "ПідпиÑатиÑÑ" #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:173 #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:308 msgid "Subscribe to the {0} feed" -msgstr "" +msgstr "ПідпиÑатиÑÑ Ð½Ð° {0} Ñтрічку" #: src/view/screens/ProfileList.tsx:604 msgid "Subscribe to this list" msgstr "ПідпиÑатиÑÑ Ð½Ð° цей ÑпиÑок" -#: src/view/com/lists/ListCard.tsx:101 -#~ msgid "Subscribed" -#~ msgstr "" - #: src/view/screens/Search/Search.tsx:374 msgid "Suggested Follows" msgstr "Пропоновані підпиÑки" #: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:64 msgid "Suggested for you" -msgstr "" +msgstr "Пропозиції Ð´Ð»Ñ Ð²Ð°Ñ" #: src/view/com/modals/SelfLabel.tsx:95 msgid "Suggestive" -msgstr "" +msgstr "ÐеприÑтойний" #: src/Navigation.tsx:214 #: src/view/screens/Support.tsx:30 @@ -4236,16 +4005,16 @@ msgstr "Перемикнути обліковий запиÑ" #: src/view/com/modals/SwitchAccount.tsx:97 #: src/view/screens/Settings/index.tsx:130 msgid "Switch to {0}" -msgstr "" +msgstr "ПереключитиÑÑ Ð½Ð° {0}" #: src/view/com/modals/SwitchAccount.tsx:98 #: src/view/screens/Settings/index.tsx:131 msgid "Switches the account you are logged in to" -msgstr "" +msgstr "Переключає обліковий запиÑ" #: src/view/screens/Settings/index.tsx:472 msgid "System" -msgstr "" +msgstr "СиÑтемне" #: src/view/screens/Settings/index.tsx:795 msgid "System log" @@ -4253,11 +4022,11 @@ msgstr "СиÑтемний журнал" #: src/components/dialogs/MutedWords.tsx:337 msgid "tag" -msgstr "" +msgstr "тег" #: src/components/TagMenu/index.tsx:78 msgid "Tag menu: {displayTag}" -msgstr "" +msgstr "Меню тегів: {displayTag}" #: src/components/TagMenu/index.tsx:74 #~ msgid "Tag menu: {tag}" @@ -4269,11 +4038,11 @@ msgstr "ВиÑоке" #: src/view/com/util/images/AutoSizedImage.tsx:70 msgid "Tap to view fully" -msgstr "" +msgstr "ТоркнітьÑÑ, щоб переглÑнути повніÑтю" #: src/screens/Onboarding/index.tsx:39 msgid "Tech" -msgstr "" +msgstr "Технології" #: src/view/shell/desktop/RightNav.tsx:81 msgid "Terms" @@ -4288,7 +4057,7 @@ msgstr "Умови ВикориÑтаннÑ" #: src/components/dialogs/MutedWords.tsx:337 msgid "text" -msgstr "" +msgstr "текÑÑ‚" #: src/view/com/modals/AppealLabel.tsx:70 #: src/view/com/modals/report/InputIssueDetails.tsx:51 @@ -4297,7 +4066,7 @@ msgstr "Поле вводу текÑту" #: src/view/com/auth/create/CreateAccount.tsx:94 msgid "That handle is already taken." -msgstr "" +msgstr "Цей пÑевдонім вже зайнÑтий." #: src/view/com/profile/ProfileHeader.tsx:263 msgid "The account will be able to interact with you after unblocking." @@ -4313,7 +4082,7 @@ msgstr "Політику захиÑту авторÑького права пер #: src/screens/Onboarding/Layout.tsx:60 msgid "The following steps will help customize your Bluesky experience." -msgstr "" +msgstr "ÐаÑтупні кроки допоможуть налаштувати Ваш доÑвід викориÑÑ‚Ð°Ð½Ð½Ñ Bluesky." #: src/view/com/post-thread/PostThread.tsx:517 msgid "The post may have been deleted." @@ -4325,11 +4094,7 @@ msgstr "Політика конфіденційноÑті була Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ #: 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 "" - -#: 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}, щоб зв'ÑзатиÑÑ Ð· нами." +msgstr "Форму підтримки переміщено. Якщо вам потрібна допомога, будь лаÑка, <0/> або відвідайте {HELP_DESK_URL}, щоб зв'ÑзатиÑÑ Ð· нами." #: src/view/screens/TermsOfService.tsx:33 msgid "The Terms of Service have been moved to" @@ -4337,19 +4102,19 @@ msgstr "Умови ВикориÑÑ‚Ð°Ð½Ð½Ñ Ð¿ÐµÑ€ÐµÐ½ÐµÑено до" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:150 msgid "There are many feeds to try:" -msgstr "" +msgstr "Також Ñ” багато інших Ñтрічок, щоб Ñпробувати:" #: src/view/screens/ProfileFeed.tsx:550 msgid "There was an an issue contacting the server, please check your internet connection and try again." -msgstr "" +msgstr "Виникла проблема з доÑтупом до Ñервера. Перевірте Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ Інтернету Ñ– повторіть Ñпробу знову." #: src/view/com/posts/FeedErrorMessage.tsx:139 msgid "There was an an issue removing this feed. Please check your internet connection and try again." -msgstr "" +msgstr "Виникла проблема при видаленні цієї Ñтрічки. Перевірте Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ Інтернету Ñ– повторіть Ñпробу." #: src/view/screens/ProfileFeed.tsx:210 msgid "There was an an issue updating your feeds, please check your internet connection and try again." -msgstr "" +msgstr "Виникла проблема з оновленнÑм ваших Ñтрічок. Перевірте Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ Інтернету Ñ– повторіть Ñпробу." #: src/view/screens/ProfileFeed.tsx:237 #: src/view/screens/ProfileList.tsx:267 @@ -4357,7 +4122,7 @@ msgstr "" #: src/view/screens/SavedFeeds.tsx:231 #: src/view/screens/SavedFeeds.tsx:252 msgid "There was an issue contacting the server" -msgstr "" +msgstr "При з'єднанні з Ñервером виникла проблема" #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:57 #: src/view/com/auth/onboarding/RecommendedFeedsItem.tsx:66 @@ -4365,33 +4130,33 @@ msgstr "" #: src/view/com/feeds/FeedSourceCard.tsx:129 #: src/view/com/feeds/FeedSourceCard.tsx:183 msgid "There was an issue contacting your server" -msgstr "" +msgstr "При з'єднанні з вашим Ñервером виникла проблема" #: src/view/com/notifications/Feed.tsx:117 msgid "There was an issue fetching notifications. Tap here to try again." -msgstr "" +msgstr "Виникла проблема з завантаженнÑм Ñповіщень. ÐатиÑніть тут, щоб повторити Ñпробу." #: src/view/com/posts/Feed.tsx:265 msgid "There was an issue fetching posts. Tap here to try again." -msgstr "" +msgstr "Виникла проблема з завантаженнÑм поÑтів. ÐатиÑніть тут, щоб повторити Ñпробу." #: src/view/com/lists/ListMembers.tsx:172 msgid "There was an issue fetching the list. Tap here to try again." -msgstr "" +msgstr "Виникла проблема з завантаженнÑм ÑпиÑку. ÐатиÑніть тут, щоб повторити Ñпробу." #: src/view/com/feeds/ProfileFeedgens.tsx:148 #: src/view/com/lists/ProfileLists.tsx:155 msgid "There was an issue fetching your lists. Tap here to try again." -msgstr "" +msgstr "Виникла проблема з завантаженнÑм ваших ÑпиÑків. ÐатиÑніть тут, щоб повторити Ñпробу." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:63 #: src/view/com/modals/ContentFilteringSettings.tsx:126 msgid "There was an issue syncing your preferences with the server" -msgstr "" +msgstr "Виникла проблема під Ñ‡Ð°Ñ Ñинхронізації ваших налаштувань із Ñервером" #: src/view/screens/AppPasswords.tsx:66 msgid "There was an issue with fetching your app passwords" -msgstr "" +msgstr "Виникла проблема з завантаженнÑм ваших паролів Ð´Ð»Ñ Ð·Ð°ÑтоÑунків" #: src/view/com/post-thread/PostThreadFollowBtn.tsx:93 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:105 @@ -4402,14 +4167,14 @@ msgstr "" #: src/view/com/profile/ProfileHeader.tsx:250 #: src/view/com/profile/ProfileHeader.tsx:272 msgid "There was an issue! {0}" -msgstr "" +msgstr "Виникла проблема! {0}" #: src/view/screens/ProfileList.tsx:288 #: src/view/screens/ProfileList.tsx:307 #: src/view/screens/ProfileList.tsx:329 #: src/view/screens/ProfileList.tsx:348 msgid "There was an issue. Please check your internet connection and try again." -msgstr "" +msgstr "Виникла проблема. Перевірте Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ Інтернету Ñ– повторіть Ñпробу." #: src/view/com/util/ErrorBoundary.tsx:36 msgid "There was an unexpected issue in the application. Please let us know if this happened to you!" @@ -4417,7 +4182,7 @@ msgstr "У заÑтоÑунку ÑталаÑÑ Ð½ÐµÐ¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð° пробл #: src/screens/Deactivated.tsx:106 msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." -msgstr "" +msgstr "ВідбувавÑÑ Ð½Ð°Ð¿Ð»Ð¸Ð² нових кориÑтувачів у Bluesky! Ми активуємо ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ñк тільки зможемо." #: src/view/com/auth/create/Step2.tsx:55 #~ msgid "There's something wrong with this number. Please choose your country and enter your full phone number!" @@ -4425,15 +4190,7 @@ msgstr "" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:138 msgid "These are popular accounts you might like:" -msgstr "" - -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:138 -#~ msgid "These are popular accounts you might like." -#~ msgstr "" - -#: src/view/com/util/moderation/LabelInfo.tsx:45 -#~ msgid "This {0} has been labeled." -#~ msgstr "" +msgstr "Ці популÑрні кориÑтувачі можуть вам ÑподобатиÑÑ:" #: src/view/com/util/moderation/ScreenHider.tsx:88 msgid "This {screenDescription} has been flagged:" @@ -4449,7 +4206,7 @@ msgstr "Цей вміÑÑ‚ розміщено {0}. Увімкнути Ð·Ð¾Ð²Ð½Ñ–Ñ #: src/view/com/modals/ModerationDetails.tsx:67 msgid "This content is not available because one of the users involved has blocked the other." -msgstr "" +msgstr "Цей контент недоÑтупний, оÑкільки один із залучених кориÑтувачів заблокував іншого." #: src/view/com/posts/FeedErrorMessage.tsx:108 msgid "This content is not viewable without a Bluesky account." @@ -4457,7 +4214,7 @@ msgstr "Цей вміÑÑ‚ не доÑтупний Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду бе #: src/view/screens/Settings/ExportCarDialog.tsx:75 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost.</0>" -msgstr "" +msgstr "Ð¦Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ð·Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑŒÑÑ Ð² беті. Ви можете дізнатиÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про екÑпорт репозиторіїв в <0>у цьому блозі.</0>" #: src/view/com/posts/FeedErrorMessage.tsx:114 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." @@ -4467,11 +4224,11 @@ msgstr "Ð¦Ñ Ñтрічка зараз отримує забагато Ð·Ð°Ð¿Ð¸Ñ #: src/view/screens/ProfileFeed.tsx:476 #: src/view/screens/ProfileList.tsx:661 msgid "This feed is empty!" -msgstr "" +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 "" +msgstr "Ð¦Ñ Ñтрічка порожнÑ! Можливо, вам треба підпиÑатиÑÑ Ð½Ð° більшу кількіÑть кориÑтувачів або змінити ваші Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ð¾Ð²Ð¸." #: src/view/com/modals/BirthDateSettings.tsx:61 msgid "This information is not shared with other users." @@ -4481,21 +4238,17 @@ msgstr "Ð¦Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð½Ðµ розкриваєтьÑÑ Ñ–Ð½ÑˆÐ¸Ð¼ кРmsgid "This is important in case you ever need to change your email or reset your password." msgstr "Це важливо Ð´Ð»Ñ Ð²Ð¸Ð¿Ð°Ð´ÐºÑƒ, Ñкщо вам коли-небудь потрібно буде змінити адреÑу електронної пошти або відновити пароль." -#: src/view/com/auth/create/Step1.tsx:55 -#~ msgid "This is the service that keeps you online." -#~ msgstr "Це ÑÐµÑ€Ð²Ñ–Ñ Ñкий зберігає дані вашого облікового запиÑу." - #: src/view/com/modals/LinkWarning.tsx:58 msgid "This link is taking you to the following website:" msgstr "Це поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð²ÐµÐ´Ðµ на Ñайт:" #: src/view/screens/ProfileList.tsx:839 msgid "This list is empty!" -msgstr "" +msgstr "СпиÑок порожній!" #: src/view/com/modals/AddAppPasswords.tsx:106 msgid "This name is already in use" -msgstr "" +msgstr "Це ім'Ñ Ð²Ð¶Ðµ викориÑтовуєтьÑÑ" #: src/view/com/post-thread/PostThreadItem.tsx:125 msgid "This post has been deleted." @@ -4503,15 +4256,15 @@ msgstr "Цей поÑÑ‚ було видалено." #: src/view/com/modals/ModerationDetails.tsx:62 msgid "This user has blocked you. You cannot view their content." -msgstr "" +msgstr "Цей кориÑтувач заблокував ваÑ. Ви не можете бачити Ñ—Ñ… поÑти." #: src/view/com/modals/ModerationDetails.tsx:42 msgid "This user is included in the <0/> list which you have blocked." -msgstr "" +msgstr "Цей кориÑтувач в ÑпиÑку \"<0/>\" на Ñкий ви підпиÑалиÑÑŒ та заблокували." #: src/view/com/modals/ModerationDetails.tsx:74 msgid "This user is included in the <0/> list which you have muted." -msgstr "" +msgstr "Цей кориÑтувач в ÑпиÑку \"<0/>\" Ñкий ви ігноруєте." #: src/view/com/modals/ModerationDetails.tsx:74 #~ msgid "This user is included the <0/> list which you have muted." @@ -4523,11 +4276,11 @@ msgstr "Це Ð¿Ð¾Ð¿ÐµÑ€ÐµÐ´Ð¶ÐµÐ½Ð½Ñ Ð´Ð¾Ñтупне тільки Ð´Ð»Ñ Ð·Ð°Ð #: src/components/dialogs/MutedWords.tsx:285 msgid "This will delete {0} from your muted words. You can always add it back later." -msgstr "" +msgstr "Це видалить {0} зі ваших ігнорованих Ñлів. Ви завжди можете додати його назад." #: src/view/com/util/forms/PostDropdownBtn.tsx:282 msgid "This will hide this post from your feeds." -msgstr "Це приховає цей поÑÑ‚ із вашої Ñтрічки." +msgstr "Це Ð´Ñ–Ñ Ð¿Ñ€Ð¸Ñ…Ð¾Ð²Ð°Ñ” цей поÑÑ‚ із вашої Ñтрічки." #: src/view/screens/PreferencesThreads.tsx:53 #: src/view/screens/Settings/index.tsx:565 @@ -4540,11 +4293,11 @@ msgstr "Режим гілок" #: src/Navigation.tsx:257 msgid "Threads Preferences" -msgstr "" +msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½ÑŒ" #: src/components/dialogs/MutedWords.tsx:113 msgid "Toggle between muted word options." -msgstr "" +msgstr "ÐŸÐµÑ€ÐµÐ¼Ð¸ÐºÐ°Ð½Ð½Ñ Ð¼Ñ–Ð¶ опціÑми Ñ–Ð³Ð½Ð¾Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñлів." #: src/view/com/util/forms/DropdownButton.tsx:246 msgid "Toggle dropdown" @@ -4564,11 +4317,7 @@ msgstr "ПереклаÑти" #: src/view/com/util/error/ErrorScreen.tsx:82 msgctxt "action" msgid "Try again" -msgstr "" - -#: src/view/com/util/error/ErrorScreen.tsx:73 -#~ msgid "Try again" -#~ msgstr "Спробувати ще раз" +msgstr "Спробувати ще раз" #: src/view/screens/ProfileList.tsx:506 msgid "Un-block list" @@ -4594,7 +4343,7 @@ msgstr "Розблокувати" #: src/view/com/profile/ProfileHeader.tsx:436 msgctxt "action" msgid "Unblock" -msgstr "" +msgstr "Розблокувати" #: src/view/com/profile/ProfileHeader.tsx:261 #: src/view/com/profile/ProfileHeader.tsx:345 @@ -4611,11 +4360,11 @@ msgstr "СкаÑувати репоÑÑ‚" #: src/view/com/profile/FollowButton.tsx:55 msgctxt "action" msgid "Unfollow" -msgstr "" +msgstr "ВідпиÑатиÑÑŒ" #: src/view/com/profile/ProfileHeader.tsx:485 msgid "Unfollow {0}" -msgstr "" +msgstr "ВідпиÑатиÑÑ Ð²Ñ–Ð´ {0}" #: src/view/com/auth/create/state.ts:262 msgid "Unfortunately, you do not meet the requirements to create an account." @@ -4623,16 +4372,16 @@ msgstr "Ðа жаль, ви не відповідаєте вимогам Ð´Ð»Ñ #: src/view/com/util/post-ctrls/PostCtrls.tsx:182 msgid "Unlike" -msgstr "" +msgstr "Прибрати вподобаннÑ" #: src/components/TagMenu/index.tsx:249 #: src/view/screens/ProfileList.tsx:597 msgid "Unmute" -msgstr "" +msgstr "Ðе ігнорувати" #: src/components/TagMenu/index.web.tsx:104 msgid "Unmute {truncatedTag}" -msgstr "" +msgstr "Ðе ігнорувати {truncatedTag}" #: src/view/com/profile/ProfileHeader.tsx:326 msgid "Unmute Account" @@ -4640,7 +4389,7 @@ msgstr "ПереÑтати ігнорувати" #: src/components/TagMenu/index.tsx:208 msgid "Unmute all {displayTag} posts" -msgstr "" +msgstr "ПереÑтати ігнорувати вÑÑ– поÑти {displayTag}" #: src/components/TagMenu/index.tsx:210 #~ msgid "Unmute all {tag} posts" @@ -4654,7 +4403,7 @@ msgstr "ПереÑтати ігнорувати" #: src/view/screens/ProfileFeed.tsx:354 #: src/view/screens/ProfileList.tsx:581 msgid "Unpin" -msgstr "" +msgstr "Відкріпити" #: src/view/screens/ProfileList.tsx:474 msgid "Unpin moderation list" @@ -4662,7 +4411,7 @@ msgstr "Відкріпити ÑпиÑок модерації" #: src/view/screens/ProfileFeed.tsx:346 msgid "Unsave" -msgstr "" +msgstr "СкаÑувати збереженнÑ" #: src/view/com/modals/UserAddRemoveLists.tsx:70 msgid "Update {displayName} in Lists" @@ -4691,12 +4440,12 @@ msgstr "ВикориÑтовувати провайдера за замовчуР#: src/view/com/modals/InAppBrowserConsent.tsx:56 #: src/view/com/modals/InAppBrowserConsent.tsx:58 msgid "Use in-app browser" -msgstr "" +msgstr "У вбудованому браузері" #: src/view/com/modals/InAppBrowserConsent.tsx:66 #: src/view/com/modals/InAppBrowserConsent.tsx:68 msgid "Use my default browser" -msgstr "" +msgstr "У звичайному браузері" #: src/view/com/modals/AddAppPasswords.tsx:155 msgid "Use this to sign into the other app along with your handle." @@ -4712,15 +4461,15 @@ msgstr "ВикориÑтано:" #: src/view/com/modals/ModerationDetails.tsx:54 msgid "User Blocked" -msgstr "" +msgstr "КориÑтувача заблоковано" #: src/view/com/modals/ModerationDetails.tsx:40 msgid "User Blocked by List" -msgstr "" +msgstr "КориÑтувача заблоковано ÑпиÑком" #: src/view/com/modals/ModerationDetails.tsx:60 msgid "User Blocks You" -msgstr "" +msgstr "КориÑтувач заблокував ваÑ" #: src/view/com/auth/create/Step2.tsx:79 msgid "User handle" @@ -4729,25 +4478,25 @@ msgstr "ПÑевдонім" #: src/view/com/lists/ListCard.tsx:85 #: src/view/com/modals/UserAddRemoveLists.tsx:198 msgid "User list by {0}" -msgstr "" +msgstr "СпиÑок кориÑтувачів від {0}" #: src/view/screens/ProfileList.tsx:763 msgid "User list by <0/>" -msgstr "" +msgstr "СпиÑок кориÑтувачів від <0/>" #: src/view/com/lists/ListCard.tsx:83 #: src/view/com/modals/UserAddRemoveLists.tsx:196 #: src/view/screens/ProfileList.tsx:761 msgid "User list by you" -msgstr "" +msgstr "СпиÑок кориÑтувачів від ваÑ" #: src/view/com/modals/CreateOrEditList.tsx:196 msgid "User list created" -msgstr "" +msgstr "СпиÑок кориÑтувачів Ñтворено" #: src/view/com/modals/CreateOrEditList.tsx:182 msgid "User list updated" -msgstr "" +msgstr "СпиÑок кориÑтувачів оновлено" #: src/view/screens/Lists.tsx:58 msgid "User Lists" @@ -4766,10 +4515,6 @@ msgstr "КориÑтувачі" msgid "users followed by <0/>" msgstr "кориÑтувачі, на Ñких підпиÑані <0/>" -#: src/view/com/threadgate/WhoCanReply.tsx:115 -#~ msgid "Users followed by <0/>" -#~ msgstr "" - #: src/view/com/modals/Threadgate.tsx:106 msgid "Users in \"{0}\"" msgstr "КориÑтувачі в «{0}»" @@ -4797,15 +4542,15 @@ msgstr "Підтвердити нову адреÑу електронної по #: src/view/com/modals/VerifyEmail.tsx:103 msgid "Verify Your Email" -msgstr "" +msgstr "Підтвердьте адреÑу вашої електронної пошти" #: src/screens/Onboarding/index.tsx:42 msgid "Video Games" -msgstr "" +msgstr "Відеоігри" #: src/view/com/profile/ProfileHeader.tsx:662 msgid "View {0}'s avatar" -msgstr "" +msgstr "ПереглÑнути аватар {0}" #: src/view/screens/Log.tsx:52 msgid "View debug entry" @@ -4813,11 +4558,11 @@ msgstr "ПереглÑнути Ð·Ð°Ð¿Ð¸Ñ Ð´Ð»Ñ Ð½Ð°Ð»Ð°Ð³Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ" #: src/view/com/posts/FeedSlice.tsx:103 msgid "View full thread" -msgstr "" +msgstr "ПереглÑнути обговореннÑ" #: src/view/com/posts/FeedErrorMessage.tsx:172 msgid "View profile" -msgstr "" +msgstr "ПереглÑнути профіль" #: src/view/com/profile/ProfileSubpageHeader.tsx:128 msgid "View the avatar" @@ -4830,31 +4575,27 @@ msgstr "Відвідати Ñайт" #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:42 #: src/view/com/modals/ContentFilteringSettings.tsx:259 msgid "Warn" -msgstr "" +msgstr "Попереджати" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:134 msgid "We also think you'll like \"For You\" by Skygaze:" -msgstr "" +msgstr "Гадаємо, вам також ÑподобаєтьÑÑ Â«For You» від Skygaze:" #: src/screens/Hashtag.tsx:132 msgid "We couldn't find any results for that hashtag." -msgstr "" +msgstr "Ми не змогли знайти жодних результатів Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ хештегу." #: src/screens/Deactivated.tsx:133 msgid "We estimate {estimatedTime} until your account is ready." -msgstr "" +msgstr "Ми оцінюємо {estimatedTime} до готовноÑті вашого облікового запиÑу." #: src/screens/Onboarding/StepFinished.tsx:93 msgid "We hope you have a wonderful time. Remember, Bluesky is:" -msgstr "" - -#: src/view/com/posts/DiscoverFallbackHeader.tsx:29 -#~ msgid "We ran out of posts from your follows. Here's the latest from" -#~ msgstr "" +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 "" +msgstr "У Ð½Ð°Ñ Ð·Ð°ÐºÑ–Ð½Ñ‡Ð¸Ð»Ð¸ÑÑ Ð´Ð¾Ð¿Ð¸Ñи у ваших підпиÑках. ОÑÑŒ оÑтанні поÑти зі Ñтрічки <0/>." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:118 #~ msgid "We recommend \"For You\" by Skygaze:" @@ -4862,47 +4603,39 @@ msgstr "" #: src/components/dialogs/MutedWords.tsx:204 msgid "We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." -msgstr "" +msgstr "Ми рекомендуємо уникати загальних Ñлів, що зʼÑвлÑютьÑÑ Ñƒ багатьох поÑтах, оÑкільки це може призвеÑти до того, що жодного поÑта не буде показано." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124 msgid "We recommend our \"Discover\" feed:" -msgstr "" +msgstr "Ми рекомендуємо Ñтрічку «Discover»:" #: src/screens/Onboarding/StepInterests/index.tsx:133 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 "" +msgstr "Ми не змогли під'єднатиÑÑ. Будь лаÑка, Ñпробуйте ще раз, щоб продовжити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñвого облікового запиÑу. Якщо помилка повторюєтьÑÑ, то ви можете пропуÑтити цей процеÑ." #: src/screens/Deactivated.tsx:137 msgid "We will let you know when your account is ready." -msgstr "" +msgstr "Ми повідомимо ваÑ, коли ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð±ÑƒÐ´Ðµ готовий." #: src/view/com/modals/AppealLabel.tsx:48 msgid "We'll look into your appeal promptly." -msgstr "" +msgstr "Ми Ñкоро розглÑнемо вашу апелÑцію." #: src/screens/Onboarding/StepInterests/index.tsx:138 msgid "We'll use this to help customize your experience." -msgstr "" +msgstr "Ми ÑкориÑтаємоÑÑ Ñ†Ð¸Ð¼, щоб підлаштувати Ваш доÑвід." #: src/view/com/auth/create/CreateAccount.tsx:134 msgid "We're so excited to have you join us!" msgstr "Ми дуже раді, що ви приєдналиÑÑ!" -#: src/view/com/posts/FeedErrorMessage.tsx:99 -#~ msgid "We're sorry, but this content is not viewable without a Bluesky account." -#~ msgstr "" - -#: src/view/com/posts/FeedErrorMessage.tsx:105 -#~ msgid "We're sorry, but this feed is currently receiving high traffic and is temporarily unavailable. Please try again later." -#~ msgstr "" - #: src/view/screens/ProfileList.tsx:86 msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." -msgstr "" +msgstr "Дуже прикро, але нам не вдалоÑÑ Ð·Ð½Ð°Ð¹Ñ‚Ð¸ цей ÑпиÑок. Якщо це продовжуєтьÑÑ, будь лаÑка, зв'ÑжітьÑÑ Ð· його автором: @{handleOrDid}." #: src/components/dialogs/MutedWords.tsx:230 msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." -msgstr "" +msgstr "Ðа жаль, ми не змогли зараз завантажити ваші ігноровані Ñлова. Будь лаÑка, Ñпробуйте ще раз." #: src/view/screens/Search/Search.tsx:254 msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." @@ -4919,7 +4652,7 @@ msgstr "ЛаÑкаво проÑимо до <0>Bluesky</0>" #: src/screens/Onboarding/StepInterests/index.tsx:130 msgid "What are your interests?" -msgstr "" +msgstr "Чим ви цікавитеÑÑŒ?" #: src/view/com/modals/report/Modal.tsx:169 msgid "What is the issue with this {collectionName}?" @@ -4943,10 +4676,6 @@ msgstr "Якими мовами ви хочете бачити поÑти у аРmsgid "Who can reply" msgstr "Хто може відповідати" -#: src/view/com/threadgate/WhoCanReply.tsx:79 -#~ msgid "Who can reply?" -#~ msgstr "" - #: src/view/com/modals/crop-image/CropImage.web.tsx:102 msgid "Wide" msgstr "Широке" @@ -4962,7 +4691,7 @@ msgstr "ÐапиÑати відповідь" #: src/screens/Onboarding/index.tsx:28 msgid "Writers" -msgstr "" +msgstr "ПиÑьменники" #: src/view/com/auth/create/Step2.tsx:263 #~ msgid "XXXXXX" @@ -4984,24 +4713,20 @@ msgstr "Так" #: src/screens/Deactivated.tsx:130 msgid "You are in line." -msgstr "" +msgstr "Ви в черзі." #: src/view/com/posts/FollowingEmptyState.tsx:67 #: src/view/com/posts/FollowingEndOfFeed.tsx:68 msgid "You can also discover new Custom Feeds to follow." -msgstr "" +msgstr "Також ви можете знайти каÑтомні Ñтрічки Ð´Ð»Ñ Ð¿Ñ–Ð´Ð¿Ð¸ÑаннÑ." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:123 #~ msgid "You can also try our \"Discover\" algorithm:" #~ msgstr "" -#: src/view/com/auth/create/Step1.tsx:106 -#~ msgid "You can change hosting providers at any time." -#~ msgstr "Ви можете змінити хоÑтинг-провайдера у будь-Ñкий чаÑ." - #: src/screens/Onboarding/StepFollowingFeed.tsx:142 msgid "You can change these settings later." -msgstr "" +msgstr "Ви можете змінити ці Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð·Ð½Ñ–ÑˆÐµ." #: src/view/com/auth/login/Login.tsx:158 #: src/view/com/auth/login/PasswordUpdatedForm.tsx:31 @@ -5030,18 +4755,18 @@ msgstr "Ви заблокували автора або автор Ð·Ð°Ð±Ð»Ð¾ÐºÑ #: src/view/com/modals/ModerationDetails.tsx:56 msgid "You have blocked this user. You cannot view their content." -msgstr "" +msgstr "Ви заблокували цього кориÑтувача. Ви не можете бачити Ñ—Ñ… вміÑÑ‚." #: src/view/com/auth/login/SetNewPasswordForm.tsx:57 #: src/view/com/auth/login/SetNewPasswordForm.tsx:92 #: src/view/com/modals/ChangePassword.tsx:87 #: src/view/com/modals/ChangePassword.tsx:121 msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." -msgstr "" +msgstr "Ви ввели неправильний код. Він має виглÑдати так: XXXXX-XXXXX." #: src/view/com/modals/ModerationDetails.tsx:87 msgid "You have muted this user." -msgstr "" +msgstr "Ви включили функцію Ñ–Ð³Ð½Ð¾Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ кориÑтувача." #: src/view/com/feeds/ProfileFeedgens.tsx:136 msgid "You have no feeds." @@ -5066,23 +4791,23 @@ msgstr "Ви ще не ігноруєте жодного облікового з #: src/components/dialogs/MutedWords.tsx:250 msgid "You haven't muted any words or tags yet" -msgstr "" +msgstr "У Ð²Ð°Ñ Ñ‰Ðµ немає ігнорованих Ñлів чи тегів" #: src/view/com/modals/ContentFilteringSettings.tsx:175 msgid "You must be 18 or older to enable adult content." -msgstr "" +msgstr "Щоб увімкнути Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð²Ð¼Ñ–Ñту Ð´Ð»Ñ Ð´Ð¾Ñ€Ð¾Ñлих вам повинно бути не менше 18 років." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:103 msgid "You must be 18 years or older to enable adult content" -msgstr "" +msgstr "Ви повинні бути Ñтарше 18 років, щоб дозволити переглÑд контенту Ð´Ð»Ñ Ð´Ð¾Ñ€Ð¾Ñлих" #: src/view/com/util/forms/PostDropdownBtn.tsx:147 msgid "You will no longer receive notifications for this thread" -msgstr "" +msgstr "Ви більше не будете отримувати ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð· цього обговореннÑ" #: src/view/com/util/forms/PostDropdownBtn.tsx:150 msgid "You will now receive notifications for this thread" -msgstr "" +msgstr "Ви будете отримувати ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð· цього обговореннÑ" #: src/view/com/auth/login/SetNewPasswordForm.tsx:107 msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password." @@ -5090,21 +4815,21 @@ msgstr "Ви отримаєте електронний лиÑÑ‚ із кодом #: src/screens/Onboarding/StepModeration/index.tsx:72 msgid "You're in control" -msgstr "" +msgstr "Ð’Ñе під вашим контролем" #: src/screens/Deactivated.tsx:87 #: src/screens/Deactivated.tsx:88 #: src/screens/Deactivated.tsx:103 msgid "You're in line" -msgstr "" +msgstr "Ви в черзі" #: src/screens/Onboarding/StepFinished.tsx:90 msgid "You're ready to go!" -msgstr "" +msgstr "Ð’Ñе готово!" #: src/view/com/posts/FollowingEndOfFeed.tsx:48 msgid "You've reached the end of your feed! Find some more accounts to follow." -msgstr "" +msgstr "Ваша Ð´Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñтрічка закінчилаÑÑŒ! ПідпишітьÑÑ Ð½Ð° більше кориÑтувачів щоб отримувати більше поÑтів." #: src/view/com/auth/create/Step1.tsx:67 msgid "Your account" @@ -5112,11 +4837,11 @@ msgstr "Ваш акаунт" #: src/view/com/modals/DeleteAccount.tsx:67 msgid "Your account has been deleted" -msgstr "" +msgstr "Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð¾" #: src/view/screens/Settings/ExportCarDialog.tsx:47 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 "" +msgstr "Дані з вашого облікового запиÑу, Ñкі міÑÑ‚Ñть уÑÑ– загальнодоÑтупні запиÑи, можна завантажити Ñк \"CAR\" файл. Цей файл не міÑтить медіафайлів, таких Ñк зображеннÑ, або оÑобиÑті дані, Ñкі необхідно отримати окремо." #: src/view/com/auth/create/Step1.tsx:215 msgid "Your birth date" @@ -5124,11 +4849,11 @@ msgstr "Ваша дата народженнÑ" #: src/view/com/modals/InAppBrowserConsent.tsx:47 msgid "Your choice will be saved, but can be changed later in settings." -msgstr "" +msgstr "Ваш вибір буде запам'Ñтовано, ви у будь-Ñкий момент зможете змінити його в налаштуваннÑÑ…." #: src/screens/Onboarding/StepFollowingFeed.tsx:61 msgid "Your default feed is \"Following\"" -msgstr "" +msgstr "Ваша Ñтрічка за замовчуваннÑм \"Following\"" #: src/view/com/auth/create/state.ts:110 #: src/view/com/auth/login/ForgotPasswordForm.tsx:70 @@ -5138,7 +4863,7 @@ msgstr "Ðе вдалоÑÑ Ñ€Ð¾Ð·Ð¿Ñ–Ð·Ð½Ð°Ñ‚Ð¸ адреÑу електронн #: src/view/com/modals/Waitlist.tsx:109 #~ msgid "Your email has been saved! We'll be in touch soon." -#~ msgstr "Вашу електронну адреÑу збережено! Ми Ñкоро зв'ÑжемоÑÑ Ð· вами." +#~ msgstr "" #: src/view/com/modals/ChangeEmail.tsx:125 msgid "Your email has been updated but not verified. As a next step, please verify your new email." @@ -5150,7 +4875,7 @@ msgstr "Ваша електронна пошта ще не підтверджеР#: src/view/com/posts/FollowingEmptyState.tsx:47 msgid "Your following feed is empty! Follow more users to see what's happening." -msgstr "" +msgstr "Ваша Ð´Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñтрічка порожнÑ! ПідпишітьÑÑ Ð½Ð° більше кориÑтувачів щоб отримувати більше поÑтів." #: src/view/com/auth/create/Step2.tsx:83 msgid "Your full handle will be" @@ -5158,29 +4883,25 @@ msgstr "Ваш повний пÑевдонім буде" #: src/view/com/modals/ChangeHandle.tsx:270 msgid "Your full handle will be <0>@{0}</0>" -msgstr "" - -#: src/view/com/auth/create/Step1.tsx:53 -#~ msgid "Your hosting provider" -#~ msgstr "Ваш хоÑтинг-провайдер" +msgstr "Вашим повним пÑевдонімом буде <0>@{0}</0>" #: src/view/screens/Settings.tsx:430 #: src/view/shell/desktop/RightNav.tsx:137 #: src/view/shell/Drawer.tsx:660 #~ msgid "Your invite codes are hidden when logged in using an App Password" -#~ msgstr "Ваші коди Ð·Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ Ð¿Ñ€Ð¸Ñ…Ð¾Ð²Ð°Ð½Ð¾, Ñкщо ви увійшли за допомогою Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð´Ð»Ñ Ð·Ð°ÑтоÑунків" +#~ msgstr "" #: src/components/dialogs/MutedWords.tsx:221 msgid "Your muted words" -msgstr "" +msgstr "Ваші ігноровані Ñлова" #: src/view/com/modals/ChangePassword.tsx:155 msgid "Your password has been changed successfully!" -msgstr "" +msgstr "Ваш пароль уÑпішно змінено!" #: src/view/com/composer/Composer.tsx:274 msgid "Your post has been published" -msgstr "" +msgstr "ПоÑÑ‚ опубліковано" #: src/screens/Onboarding/StepFinished.tsx:105 #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:59 @@ -5193,22 +4914,11 @@ msgstr "Ваші повідомленнÑ, Ð²Ð¿Ð¾Ð´Ð¾Ð±Ð°Ð½Ð½Ñ Ñ– блоки Ñ” msgid "Your profile" msgstr "Ваш профіль" -#: src/view/screens/Moderation.tsx:205 -#~ msgid "Your profile and account will not be visible to anyone visiting the Bluesky app without an account, or to account holders who are not logged in. Enabling this will not make your profile private." -#~ msgstr "" - -#: src/view/screens/Moderation.tsx:220 -#~ msgid "Your profile and content will not be visible to anyone visiting the Bluesky app without an account. Enabling this will not make your profile private." -#~ msgstr "" - -#: src/view/screens/Moderation.tsx:220 -#~ msgid "Your profile and posts will not be visible to people visiting the Bluesky app or website without having an account and being logged in." -#~ msgstr "" - #: src/view/com/composer/Composer.tsx:273 msgid "Your reply has been published" -msgstr "" +msgstr "Відповідь опубліковано" #: src/view/com/auth/create/Step2.tsx:65 msgid "Your user handle" msgstr "Ваш пÑевдонім" + diff --git a/src/locale/locales/zh-CN/messages.po b/src/locale/locales/zh-CN/messages.po index eac822f05..d7fb15b03 100644 --- a/src/locale/locales/zh-CN/messages.po +++ b/src/locale/locales/zh-CN/messages.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"POT-Creation-Date: 2024-02-07 19:20+0800\n" +"POT-Creation-Date: 2024-02-17 21:00+0800\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -23,7 +23,7 @@ msgstr "(没有邮件)" #: src/view/com/profile/ProfileHeader.tsx:593 msgid "{following} following" -msgstr "{following} æ£åœ¨å…³æ³¨" +msgstr "{following} 个æ£åœ¨å…³æ³¨" #: src/view/shell/desktop/RightNav.tsx:151 #~ msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" @@ -41,15 +41,15 @@ msgstr "{following} æ£åœ¨å…³æ³¨" #: src/view/shell/Drawer.tsx:440 msgid "{numUnreadNotifications} unread" -msgstr "{numUnreadNotifications} 未读" +msgstr "{numUnreadNotifications} 个未读" #: src/view/com/threadgate/WhoCanReply.tsx:158 msgid "<0/> members" -msgstr "<0/> æˆå‘˜" +msgstr "<0/> 个æˆå‘˜" #: src/view/com/profile/ProfileHeader.tsx:595 msgid "<0>{following} </0><1>following</1>" -msgstr "<0>{following} </0><1>æ£åœ¨å…³æ³¨</1>" +msgstr "<0>{following} </0><1>个æ£åœ¨å…³æ³¨</1>" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:30 msgid "<0>Choose your</0><1>Recommended</1><2>Feeds</2>" @@ -69,11 +69,11 @@ msgstr "âš æ— æ•ˆçš„ç”¨æˆ·è¯†åˆ«ç¬¦" #: src/view/com/util/moderation/LabelInfo.tsx:45 msgid "A content warning has been applied to this {0}." -msgstr "æ¤å¤„å·²å¯ç”¨å†…容è¦å‘Š {0}." +msgstr "内容è¦å‘Šå·²å¥—用到这个{0}." #: src/lib/hooks/useOTAUpdate.ts:16 msgid "A new version of the app is available. Please update to continue using the app." -msgstr "App 新版本已å‘布,请更新以继ç»ä½¿ç”¨ã€‚" +msgstr "应用新版本已å‘布,请更新以继ç»ä½¿ç”¨ã€‚" #: src/view/com/util/ViewHeader.tsx:89 #: src/view/screens/Search/Search.tsx:647 @@ -137,7 +137,7 @@ msgstr "æ·»åŠ " #: src/view/com/modals/SelfLabel.tsx:56 msgid "Add a content warning" -msgstr "æ·»åŠ å†…å®¹è¦å‘Š" +msgstr "新增内容è¦å‘Š" #: src/view/screens/ProfileList.tsx:803 msgid "Add a user to this list" @@ -152,18 +152,18 @@ msgstr "æ·»åŠ è´¦æˆ·" #: src/view/com/composer/photos/Gallery.tsx:180 #: src/view/com/modals/AltImage.tsx:116 msgid "Add alt text" -msgstr "æ·»åŠ æ›¿ä»£æ–‡å—" +msgstr "新增替代文å—" #: src/view/screens/AppPasswords.tsx:102 #: src/view/screens/AppPasswords.tsx:143 #: src/view/screens/AppPasswords.tsx:156 msgid "Add App Password" -msgstr "æ·»åŠ App 专用密ç " +msgstr "新增应用专用密ç " #: src/view/com/modals/report/InputIssueDetails.tsx:41 #: src/view/com/modals/report/Modal.tsx:191 msgid "Add details" -msgstr "æ·»åŠ ç»†èŠ‚" +msgstr "新增细节" #: src/view/com/modals/report/Modal.tsx:194 msgid "Add details to report" @@ -179,15 +179,15 @@ msgstr "æ·»åŠ é“¾æŽ¥å¡ç‰‡:" #: src/components/dialogs/MutedWords.tsx:158 msgid "Add mute word for configured settings" -msgstr "" +msgstr "为é…ç½®çš„è®¾ç½®æ·»åŠ éšè—è¯" #: src/components/dialogs/MutedWords.tsx:87 msgid "Add muted words and tags" -msgstr "" +msgstr "æ·»åŠ éšè—è¯å’Œè¯é¢˜æ ‡ç¾" #: src/view/com/modals/ChangeHandle.tsx:417 msgid "Add the following DNS record to your domain:" -msgstr "将以下DNSè®°å½•æ·»åŠ åˆ°ä½ çš„åŸŸå:" +msgstr "将以下 DNS è®°å½•æ–°å¢žåˆ°ä½ çš„åŸŸå:" #: src/view/com/profile/ProfileHeader.tsx:310 msgid "Add to Lists" @@ -205,15 +205,15 @@ msgstr "å·²æ·»åŠ " #: src/view/com/modals/ListAddRemoveUsers.tsx:191 #: src/view/com/modals/UserAddRemoveLists.tsx:144 msgid "Added to list" -msgstr "æ·»åŠ è‡³åˆ—è¡¨" +msgstr "å·²æ·»åŠ è‡³åˆ—è¡¨" #: src/view/com/feeds/FeedSourceCard.tsx:127 msgid "Added to my feeds" -msgstr "æ·»åŠ è‡³è‡ªå®šä¹‰ä¿¡æ¯æµ" +msgstr "å·²æ·»åŠ è‡³è‡ªå®šä¹‰ä¿¡æ¯æµ" #: src/view/screens/PreferencesFollowingFeed.tsx:173 msgid "Adjust the number of likes a reply must have to be shown in your feed." -msgstr "调整回å¤ä¸éœ€è¦å…·æœ‰çš„点赞数æ‰ä¼šåœ¨ä½ çš„ä¿¡æ¯æµä¸æ˜¾ç¤ºã€‚" +msgstr "调整回å¤ä¸éœ€è¦å…·æœ‰çš„喜欢数æ‰ä¼šåœ¨ä½ çš„ä¿¡æ¯æµä¸æ˜¾ç¤ºã€‚" #: src/view/com/modals/SelfLabel.tsx:75 msgid "Adult Content" @@ -221,7 +221,7 @@ msgstr "æˆäººå†…容" #: src/view/com/modals/ContentFilteringSettings.tsx:141 msgid "Adult content can only be enabled via the Web at <0/>." -msgstr "è¦æ˜¾ç¤ºæˆäººå†…å®¹ï¼Œä½ å¿…é¡»è®¿é—®ç½‘é¡µç«¯ä¸Šçš„<0/>。" +msgstr "è¦æ˜¾ç¤ºæˆäººå†…å®¹ï¼Œä½ å¿…é¡»è®¿é—®ç½‘é¡µç«¯<0/>æ¥å¯ç”¨ã€‚" #: src/view/screens/Settings/index.tsx:664 msgid "Advanced" @@ -229,12 +229,12 @@ msgstr "详细设置" #: src/view/screens/Feeds.tsx:666 msgid "All the feeds you've saved, right in one place." -msgstr "" +msgstr "ä½ ä¿å˜çš„æ‰€æœ‰ä¿¡æ¯æµéƒ½é›†ä¸åœ¨ä¸€å¤„。" #: src/view/com/auth/login/ForgotPasswordForm.tsx:221 #: src/view/com/modals/ChangePassword.tsx:168 msgid "Already have a code?" -msgstr "å·²ç»æœ‰ç¡®è®¤ç 了?" +msgstr "å·²ç»æœ‰éªŒè¯ç 了?" #: src/view/com/auth/login/ChooseAccountForm.tsx:98 msgid "Already signed in as @{0}" @@ -250,7 +250,7 @@ msgstr "替代文å—" #: src/view/com/composer/photos/Gallery.tsx:209 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:124 msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below." @@ -280,29 +280,29 @@ msgstr "应用è¯è¨€" #: src/view/screens/AppPasswords.tsx:228 msgid "App password deleted" -msgstr "App 专用密ç å·²åˆ é™¤" +msgstr "应用专用密ç å·²åˆ é™¤" #: src/view/com/modals/AddAppPasswords.tsx:134 msgid "App Password names can only contain letters, numbers, spaces, dashes, and underscores." -msgstr "App 专用密ç åªèƒ½åŒ…å«å—æ¯ã€æ•°å—ã€ç©ºæ ¼ã€ç ´æŠ˜å·åŠä¸‹åˆ’线。" +msgstr "应用专用密ç åªèƒ½åŒ…å«å—æ¯ã€æ•°å—ã€ç©ºæ ¼ã€ç ´æŠ˜å·åŠä¸‹åˆ’线。" #: src/view/com/modals/AddAppPasswords.tsx:99 msgid "App Password names must be at least 4 characters long." -msgstr "App 专用密ç 必须至少为 4 个å—符。" +msgstr "应用专用密ç 必须至少为 4 个å—符。" #: src/view/screens/Settings/index.tsx:675 msgid "App password settings" -msgstr "App 专用密ç 设置" +msgstr "应用专用密ç 设置" #: src/view/screens/Settings.tsx:650 #~ msgid "App passwords" -#~ msgstr "App 专用密ç " +#~ msgstr "应用专用密ç " #: src/Navigation.tsx:239 #: src/view/screens/AppPasswords.tsx:187 #: src/view/screens/Settings/index.tsx:684 msgid "App Passwords" -msgstr "App 专用密ç " +msgstr "应用专用密ç " #: src/view/com/util/forms/PostDropdownBtn.tsx:337 #: src/view/com/util/forms/PostDropdownBtn.tsx:346 @@ -327,7 +327,7 @@ msgstr "外观" #: src/view/screens/AppPasswords.tsx:224 msgid "Are you sure you want to delete the app password \"{name}\"?" -msgstr "ä½ ç¡®å®šè¦åˆ é™¤è¿™æ¡ App 专用密ç \"{name}\"?" +msgstr "ä½ ç¡®å®šè¦åˆ 除这æ¡åº”用专用密ç \"{name}\"?" #: src/view/com/composer/Composer.tsx:150 msgid "Are you sure you'd like to discard this draft?" @@ -388,7 +388,7 @@ msgstr "生日" #: src/view/screens/Settings/index.tsx:340 msgid "Birthday:" -msgstr "生日:" +msgstr "生日:" #: src/view/com/profile/ProfileHeader.tsx:239 #: src/view/com/profile/ProfileHeader.tsx:346 @@ -439,7 +439,7 @@ msgstr "å·²å±è”½å¸–å。" #: src/view/screens/ProfileList.tsx:318 msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." -msgstr "å±è”½æ˜¯å…¬å…±æ€§çš„。被å±è”½çš„è´¦æˆ·æ— æ³•åœ¨ä½ çš„å¸–åä¸å›žå¤ã€æåŠä½ 或以其他方å¼ä¸Žä½ 互动。" +msgstr "å±è”½æ˜¯å…¬å¼€çš„。被å±è”½çš„è´¦æˆ·æ— æ³•åœ¨ä½ çš„å¸–åä¸å›žå¤ã€æåŠä½ 或以其他方å¼ä¸Žä½ 互动。" #: src/view/com/auth/HomeLoggedOutCTA.tsx:93 #: src/view/com/auth/SplashScreen.web.tsx:133 @@ -454,7 +454,7 @@ msgstr "Bluesky" #: src/view/com/auth/server-input/index.tsx:150 msgid "Bluesky is an open network where you can choose your hosting provider. Custom hosting is now available in beta for developers." -msgstr "" +msgstr "Bluesky æ˜¯ä¸€ä¸ªå¼€æ”¾çš„å…¬å…±ç½‘ç»œï¼Œä½ å¯ä»¥é€‰æ‹©è‡ªå·±çš„æ‰˜ç®¡æä¾›å•†ã€‚现在,自定义托管现在已ç»è¿›å…¥å¼€å‘者测试阶段。" #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:80 @@ -558,32 +558,32 @@ msgstr "å–æ¶ˆ" #: src/view/com/modals/DeleteAccount.tsx:152 #: src/view/com/modals/DeleteAccount.tsx:230 msgid "Cancel account deletion" -msgstr "æ’¤é”€è´¦æˆ·åˆ é™¤ç”³è¯·" +msgstr "å–æ¶ˆè´¦æˆ·åˆ 除申请" #: src/view/com/modals/ChangeHandle.tsx:149 msgid "Cancel change handle" -msgstr "撤销修改用户识别符" +msgstr "å–æ¶ˆä¿®æ”¹ç”¨æˆ·è¯†åˆ«ç¬¦" #: src/view/com/modals/crop-image/CropImage.web.tsx:134 msgid "Cancel image crop" -msgstr "撤销图片è£å‰ª" +msgstr "å–æ¶ˆè£å‰ªå›¾ç‰‡" #: src/view/com/modals/EditProfile.tsx:244 msgid "Cancel profile editing" -msgstr "撤销个人资料编辑" +msgstr "å–æ¶ˆç¼–辑个人资料" #: src/view/com/modals/Repost.tsx:78 msgid "Cancel quote post" -msgstr "撤销引用帖å" +msgstr "å–æ¶ˆå¼•用帖å" #: src/view/com/modals/ListAddRemoveUsers.tsx:87 #: src/view/shell/desktop/Search.tsx:234 msgid "Cancel search" -msgstr "撤销æœç´¢" +msgstr "å–æ¶ˆæœç´¢" #: src/view/com/modals/Waitlist.tsx:136 -#~ msgid "Cancel waitlist signup" -#~ msgstr "撤销候补列表申请" +msgid "Cancel waitlist signup" +msgstr "å–æ¶ˆå€™è¡¥åˆ—表申请" #: src/view/screens/Settings/index.tsx:334 msgctxt "action" @@ -630,7 +630,7 @@ msgstr "检查我的状æ€" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:121 msgid "Check out some recommended feeds. Tap + to add them to your list of pinned feeds." -msgstr "查看一些推èçš„ä¿¡æ¯æµã€‚点击 + åŽ»å°†ä»–ä»¬æ·»åŠ åˆ°ä½ çš„å›ºå®šä¿¡æ¯æµåˆ—表ä¸ã€‚" +msgstr "查看一些推èçš„ä¿¡æ¯æµã€‚点击 + åŽ»å°†ä»–ä»¬æ–°å¢žåˆ°ä½ çš„å›ºå®šä¿¡æ¯æµåˆ—表ä¸ã€‚" #: src/view/com/auth/onboarding/RecommendedFollows.tsx:185 msgid "Check out some recommended users. Follow them to see similar users." @@ -663,7 +663,7 @@ msgstr "é€‰æ‹©å¯æ”¹è¿›ä½ è‡ªå®šä¹‰ä¿¡æ¯æµçš„算法。" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:103 msgid "Choose your main feeds" -msgstr "é€‰æ‹©ä½ çš„é¦–é€‰ä¿¡æ¯æµ" +msgstr "é€‰æ‹©ä½ çš„ä¸»è¦ä¿¡æ¯æµ" #: src/view/com/auth/create/Step1.tsx:196 msgid "Choose your password" @@ -724,7 +724,7 @@ msgstr "å…³é—è¦å‘Š" #: src/view/com/util/BottomSheetCustomBackdrop.tsx:33 msgid "Close bottom drawer" -msgstr "å…³é—åº•æ æŠ½å±‰" +msgstr "å…³é—底部抽屉" #: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:26 msgid "Close image" @@ -740,11 +740,11 @@ msgstr "å…³é—导航页脚" #: src/components/TagMenu/index.tsx:262 msgid "Close this dialog" -msgstr "" +msgstr "å…³é—该窗å£" #: src/view/shell/index.web.tsx:52 msgid "Closes bottom navigation bar" -msgstr "å…³é—底æ " +msgstr "å…³é—底部导航æ " #: src/view/com/auth/login/PasswordUpdatedForm.tsx:39 msgid "Closes password update alert" @@ -781,7 +781,7 @@ msgstr "完æˆå¼•å¯¼å¹¶å¼€å§‹ä½¿ç”¨ä½ çš„è´¦æˆ·" #: src/view/com/auth/create/Step3.tsx:73 msgid "Complete the challenge" -msgstr "" +msgstr "完æˆéªŒè¯" #: src/view/com/composer/Composer.tsx:424 msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" @@ -896,7 +896,7 @@ msgstr "ç»§ç»ä¸‹ä¸€æ¥" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:191 msgid "Continue to the next step without following any accounts" -msgstr "ä¸å…³æ³¨ä»»ä½•账户,继ç»ä¸‹ä¸€æ¥" +msgstr "ç»§ç»ä¸‹ä¸€æ¥ï¼Œä¸å…³æ³¨ä»»ä½•账户" #: src/screens/Onboarding/index.tsx:44 msgid "Cooking" @@ -919,7 +919,7 @@ msgstr "å·²å¤åˆ¶è‡³å‰ªè´´æ¿" #: src/view/com/modals/AddAppPasswords.tsx:189 msgid "Copies app password" -msgstr "å·²å¤åˆ¶ App 专用密ç " +msgstr "å·²å¤åˆ¶åº”用专用密ç " #: src/view/com/modals/AddAppPasswords.tsx:188 msgid "Copy" @@ -976,7 +976,7 @@ msgstr "创建账户" #: src/view/com/modals/AddAppPasswords.tsx:226 msgid "Create App Password" -msgstr "创建 App 专用密ç " +msgstr "创建应用专用密ç " #: src/view/com/auth/HomeLoggedOutCTA.tsx:54 #: src/view/com/auth/SplashScreen.tsx:68 @@ -1006,7 +1006,7 @@ msgstr "文化" #: src/view/com/auth/server-input/index.tsx:95 #: src/view/com/auth/server-input/index.tsx:96 msgid "Custom" -msgstr "" +msgstr "自定义" #: src/view/com/modals/ChangeHandle.tsx:389 msgid "Custom domain" @@ -1015,7 +1015,7 @@ msgstr "自定义域å" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:106 #: src/view/screens/Feeds.tsx:692 msgid "Custom feeds built by the community bring you new experiences and help you find the content you love." -msgstr "ç”±ç¤¾åŒºæž„å»ºçš„è‡ªå®šä¹‰ä¿¡æ¯æµèƒ½ä¸ºä½ å¸¦æ¥æ–°çš„ä½“éªŒï¼Œå¹¶å¸®åŠ©ä½ æ‰¾åˆ°ä½ å–œæ¬¢çš„å†…å®¹ã€‚" +msgstr "ç”±ç¤¾ç¾¤æž„å»ºçš„è‡ªå®šä¹‰ä¿¡æ¯æµèƒ½ä¸ºä½ å¸¦æ¥æ–°çš„ä½“éªŒï¼Œå¹¶å¸®åŠ©ä½ æ‰¾åˆ°ä½ å–œæ¬¢çš„å†…å®¹ã€‚" #: src/view/screens/PreferencesExternalEmbeds.tsx:55 msgid "Customize media from external sites." @@ -1053,7 +1053,7 @@ msgstr "åˆ é™¤è´¦å·" #: src/view/screens/AppPasswords.tsx:222 #: src/view/screens/AppPasswords.tsx:242 msgid "Delete app password" -msgstr "åˆ é™¤ App 专用密ç " +msgstr "åˆ é™¤åº”ç”¨ä¸“ç”¨å¯†ç " #: src/view/screens/ProfileList.tsx:364 #: src/view/screens/ProfileList.tsx:445 @@ -1131,7 +1131,7 @@ msgstr "æŽ¢ç´¢æ–°çš„è‡ªå®šä¹‰ä¿¡æ¯æµ" #: src/view/screens/Feeds.tsx:689 msgid "Discover New Feeds" -msgstr "" +msgstr "æŽ¢ç´¢æ–°çš„ä¿¡æ¯æµ" #: src/view/com/modals/EditProfile.tsx:192 msgid "Display name" @@ -1188,44 +1188,44 @@ msgstr "åŒå‡»ä»¥ç™»å½•" #: src/view/screens/Settings/index.tsx:755 msgid "Download Bluesky account data (repository)" -msgstr "" +msgstr "ä¸‹è½½ä½ çš„ Bluesky 账户数æ®ï¼ˆæ•°æ®åº“)" #: src/view/screens/Settings/ExportCarDialog.tsx:59 #: src/view/screens/Settings/ExportCarDialog.tsx:63 msgid "Download CAR file" -msgstr "" +msgstr "下载 CAR 文件" #: src/view/com/composer/text-input/TextInput.web.tsx:249 msgid "Drop to add images" -msgstr "拖放å³å¯æ·»åŠ å›¾ç‰‡" +msgstr "拖放å³å¯æ–°å¢žå›¾ç‰‡" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:111 msgid "Due to Apple policies, adult content can only be enabled on the web after completing sign up." -msgstr "å— Apple 政ç–é™åˆ¶ï¼Œæˆäººå†…容åªèƒ½åœ¨å®Œæˆæ³¨å†ŒåŽåœ¨ç½‘页端å¯ç”¨æ˜¾ç¤ºã€‚" +msgstr "å— Apple 政ç–é™åˆ¶ï¼Œæ˜¾ç¤ºæˆäººå†…容åªèƒ½åœ¨å®Œæˆæ³¨å†ŒåŽåœ¨ç½‘页端设置ä¸å¯ç”¨ã€‚" #: src/view/com/modals/EditProfile.tsx:185 msgid "e.g. Alice Roberts" -msgstr "ä¾‹ï¼šå¼ è“天" +msgstr "ä¾‹å¦‚ï¼šå¼ è“天" #: src/view/com/modals/EditProfile.tsx:203 msgid "e.g. Artist, dog-lover, and avid reader." -msgstr "例:普通艺术家一枚,喜欢撸猫。" +msgstr "例如:艺术家ã€çˆ±ç‹—人士和狂çƒè¯»è€…。" #: src/view/com/modals/CreateOrEditList.tsx:283 msgid "e.g. Great Posters" -msgstr "例:å‘布é‡è¦å¸–å的用户" +msgstr "例如:优秀的å‘帖者" #: src/view/com/modals/CreateOrEditList.tsx:284 msgid "e.g. Spammers" -msgstr "ä¾‹ï¼šåžƒåœ¾å†…å®¹åˆ¶é€ è€…" +msgstr "ä¾‹å¦‚ï¼šåžƒåœ¾å†…å®¹åˆ¶é€ è€…" #: src/view/com/modals/CreateOrEditList.tsx:312 msgid "e.g. The posters who never miss." -msgstr "ä¾‹ï¼šä½ ç»ä¸èƒ½é”™è¿‡å…¶å‘布帖å的用户。" +msgstr "例如:ç»ä¸å®¹é”™è¿‡çš„å‘帖者。" #: src/view/com/modals/CreateOrEditList.tsx:313 msgid "e.g. Users that repeatedly reply with ads." -msgstr "例:散布广告内容的用户。" +msgstr "例如:散布广告内容的用户。" #: src/view/com/modals/InviteCodes.tsx:96 msgid "Each code works once. You'll receive more invite codes periodically." @@ -1261,11 +1261,11 @@ msgstr "编辑个人资料" #: src/view/com/profile/ProfileHeader.tsx:418 msgid "Edit profile" -msgstr "编辑资料" +msgstr "编辑个人资料" #: src/view/com/profile/ProfileHeader.tsx:423 msgid "Edit Profile" -msgstr "编辑资料" +msgstr "编辑个人资料" #: src/view/com/home/HomeHeaderLayout.web.tsx:62 #: src/view/screens/Feeds.tsx:355 @@ -1343,16 +1343,16 @@ msgstr "å¯ç”¨æ¤è®¾ç½®ä»¥ä»…æŸ¥çœ‹ä½ å…³æ³¨çš„äººä¹‹é—´çš„å›žå¤ã€‚" #: src/view/screens/Profile.tsx:455 msgid "End of feed" -msgstr "结æŸä¿¡æ¯æµ" +msgstr "ä¿¡æ¯æµçš„æœ«å°¾" #: src/view/com/modals/AddAppPasswords.tsx:166 msgid "Enter a name for this App Password" -msgstr "ä¸ºæ¤ App 专用密ç 命å" +msgstr "为æ¤åº”用专用密ç 命å" #: src/components/dialogs/MutedWords.tsx:100 #: src/components/dialogs/MutedWords.tsx:101 msgid "Enter a word or tag" -msgstr "" +msgstr "è¾“å…¥ä¸€ä¸ªè¯æˆ–æ ‡ç¾" #: src/view/com/modals/VerifyEmail.tsx:105 msgid "Enter Confirmation Code" @@ -1368,7 +1368,7 @@ msgstr "è¾“å…¥ä½ æƒ³ä½¿ç”¨çš„åŸŸå" #: src/view/com/auth/login/ForgotPasswordForm.tsx:107 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 "è¾“å…¥ä½ ç”¨äºŽåˆ›å»ºè´¦æˆ·çš„ç”µå邮箱。我们将å‘ä½ å‘é€é‡ç½®ç ï¼Œä»¥ä¾¿ä½ é‡æ–°è®¾ç½®å¯†ç 。" +msgstr "è¾“å…¥ä½ ç”¨äºŽåˆ›å»ºè´¦æˆ·çš„ç”µå邮箱。我们将å‘ä½ å‘é€ç”¨äºŽå¯†ç é‡è®¾çš„确认ç 。" #: src/view/com/auth/create/Step1.tsx:228 #: src/view/com/modals/BirthDateSettings.tsx:74 @@ -1401,7 +1401,7 @@ msgstr "è¾“å…¥ä½ çš„ç”¨æˆ·å和密ç " #: src/view/com/auth/create/Step3.tsx:67 msgid "Error receiving captcha response." -msgstr "" +msgstr "Captcha å“应错误" #: src/view/screens/Search/Search.tsx:110 msgid "Error:" @@ -1422,7 +1422,7 @@ msgstr "退出图片查看器" #: src/view/com/modals/ListAddRemoveUsers.tsx:88 #: src/view/shell/desktop/Search.tsx:235 msgid "Exits inputting search query" -msgstr "退出æœç´¢æŸ¥è¯¢" +msgstr "退出æœç´¢æŸ¥è¯¢è¾“å…¥" #: src/view/com/modals/Waitlist.tsx:138 #~ msgid "Exits signing up for waitlist with {email}" @@ -1439,12 +1439,12 @@ msgstr "展开或折å ä½ è¦å›žå¤çš„完整帖å" #: src/view/screens/Settings/index.tsx:753 msgid "Export my data" -msgstr "" +msgstr "å¯¼å‡ºè´¦å·æ•°æ®" #: src/view/screens/Settings/ExportCarDialog.tsx:44 #: src/view/screens/Settings/index.tsx:764 msgid "Export My Data" -msgstr "" +msgstr "å¯¼å‡ºè´¦å·æ•°æ®" #: src/view/com/modals/EmbedConsent.tsx:64 msgid "External Media" @@ -1468,7 +1468,7 @@ msgstr "外部媒体设置" #: src/view/com/modals/AddAppPasswords.tsx:115 #: src/view/com/modals/AddAppPasswords.tsx:119 msgid "Failed to create app password." -msgstr "创建 App 专用密ç 失败。" +msgstr "创建应用专用密ç 失败。" #: src/view/com/modals/CreateOrEditList.tsx:206 msgid "Failed to create the list. Check your internet connection and try again." @@ -1489,7 +1489,7 @@ msgstr "ä¿¡æ¯æµ" #: src/view/com/feeds/FeedSourceCard.tsx:231 msgid "Feed by {0}" -msgstr "ä¿¡æ¯æµç”± {0} 创建" +msgstr "ç”± {0} åˆ›å»ºçš„ä¿¡æ¯æµ" #: src/view/screens/Feeds.tsx:605 msgid "Feed offline" @@ -1529,7 +1529,7 @@ msgstr "åˆ›å»ºä¿¡æ¯æµè¦æ±‚一些编程基础。查看 <0/> 以获å–详情。 #: src/screens/Onboarding/StepTopicalFeeds.tsx:76 msgid "Feeds can be topical as well!" -msgstr "ä¿¡æ¯æµä¹Ÿå¯ä»¥æ˜¯è¯é¢˜æ€§çš„ï¼" +msgstr "ä¿¡æ¯æµä¹Ÿå¯ä»¥å›´ç»•æŸäº›è¯é¢˜ï¼" #: src/screens/Onboarding/StepFinished.tsx:151 msgid "Finalizing" @@ -1547,23 +1547,23 @@ msgstr "寻找一些æ£åœ¨ä½¿ç”¨ Bluesky 的用户" #: src/view/screens/Search/Search.tsx:438 msgid "Find users with the search tool on the right" -msgstr "使用å³ä¾§çš„å·¥å…·æ¥æœç´¢ç”¨æˆ·" +msgstr "使用å³ä¾§çš„æœç´¢å·¥å…·æ¥æŸ¥æ‰¾ç”¨æˆ·" #: src/view/com/auth/onboarding/RecommendedFollowsItem.tsx:150 msgid "Finding similar accounts..." -msgstr "寻找类似的账户..." +msgstr "æ£åœ¨å¯»æ‰¾ç±»ä¼¼çš„账户..." #: src/view/screens/PreferencesFollowingFeed.tsx:111 msgid "Fine-tune the content you see on your Following feed." -msgstr "" +msgstr "è°ƒæ•´ä½ åœ¨å…³æ³¨ä¿¡æ¯æµä¸Šæ‰€çœ‹åˆ°çš„内容。" #: src/view/screens/PreferencesHomeFeed.tsx:111 #~ msgid "Fine-tune the content you see on your home screen." -#~ msgstr "å¾®è°ƒä½ åœ¨ä¸»é¡µä¸Šæ‰€çœ‹åˆ°çš„å†…å®¹ã€‚" +#~ msgstr "è°ƒæ•´ä½ åœ¨ä¸»é¡µä¸Šæ‰€çœ‹åˆ°çš„å†…å®¹ã€‚" #: src/view/screens/PreferencesThreads.tsx:60 msgid "Fine-tune the discussion threads." -msgstr "微调讨论主题。" +msgstr "调整讨论主题。" #: src/screens/Onboarding/index.tsx:38 msgid "Fitness" @@ -1625,7 +1625,7 @@ msgstr "ä»…é™å·²å…³æ³¨çš„用户" #: src/view/com/notifications/FeedItem.tsx:166 msgid "followed you" -msgstr "已关注" +msgstr "å…³æ³¨äº†ä½ " #: src/view/screens/ProfileFollowers.tsx:25 msgid "Followers" @@ -1647,15 +1647,15 @@ msgstr "æ£åœ¨å…³æ³¨ {0}" #: src/view/screens/PreferencesFollowingFeed.tsx:104 #: src/view/screens/Settings/index.tsx:543 msgid "Following Feed Preferences" -msgstr "" +msgstr "å…³æ³¨ä¿¡æ¯æµé¦–选项" #: src/view/com/profile/ProfileHeader.tsx:546 msgid "Follows you" -msgstr "已关注" +msgstr "å…³æ³¨äº†ä½ " #: src/view/com/profile/ProfileCard.tsx:141 msgid "Follows You" -msgstr "已关注" +msgstr "å…³æ³¨äº†ä½ " #: src/screens/Onboarding/index.tsx:43 msgid "Food" @@ -1685,7 +1685,7 @@ msgstr "忘记密ç " #: src/screens/Hashtag.tsx:108 #: src/screens/Hashtag.tsx:148 msgid "From @{sanitizedAuthor}" -msgstr "" +msgstr "æ¥è‡ª @{sanitizedAuthor}" #: src/view/com/posts/FeedItem.tsx:189 msgctxt "from-feed" @@ -1723,7 +1723,7 @@ msgstr "返回上一æ¥" #: src/view/screens/Search/Search.tsx:747 #: src/view/shell/desktop/Search.tsx:262 msgid "Go to @{queryMaybeHandle}" -msgstr "转到 @{queryMaybeHandle}" +msgstr "å‰å¾€ @{queryMaybeHandle}" #: src/view/com/auth/login/ForgotPasswordForm.tsx:189 #: src/view/com/auth/login/ForgotPasswordForm.tsx:218 @@ -1731,7 +1731,7 @@ msgstr "转到 @{queryMaybeHandle}" #: src/view/com/auth/login/SetNewPasswordForm.tsx:195 #: src/view/com/modals/ChangePassword.tsx:165 msgid "Go to next" -msgstr "转到下一个" +msgstr "å‰å¾€ä¸‹ä¸€æ¥" #: src/view/com/modals/ChangeHandle.tsx:265 msgid "Handle" @@ -1739,15 +1739,15 @@ msgstr "用户识别符" #: src/Navigation.tsx:270 msgid "Hashtag" -msgstr "" +msgstr "è¯é¢˜æ ‡ç¾" #: src/components/RichText.tsx:188 #~ msgid "Hashtag: {tag}" -#~ msgstr "" +#~ msgstr "è¯é¢˜æ ‡ç¾ï¼š{tag}" #: src/components/RichText.tsx:190 msgid "Hashtag: #{tag}" -msgstr "" +msgstr "è¯é¢˜æ ‡ç¾ï¼š#{tag}" #: src/view/com/auth/create/CreateAccount.tsx:208 msgid "Having trouble?" @@ -1760,19 +1760,19 @@ msgstr "帮助" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:132 msgid "Here are some accounts for you to follow" -msgstr "这里是一些推è关注的用户" +msgstr "这里有一些推è关注的用户" #: src/screens/Onboarding/StepTopicalFeeds.tsx:85 msgid "Here are some popular topical feeds. You can choose to follow as many as you like." -msgstr "这里是一些æµè¡Œçš„ä¿¡æ¯æµä¾›ä½ 挑选。" +msgstr "这里有一些æµè¡Œçš„ä¿¡æ¯æµä¾›ä½ 挑选。" #: src/screens/Onboarding/StepTopicalFeeds.tsx:80 msgid "Here are some topical feeds based on your interests: {interestsText}. You can choose to follow as many as you like." -msgstr "è¿™é‡Œæ˜¯ä¸€äº›åŸºäºŽä½ å…´è¶£æ‰€æŽ¨èçš„ä¿¡æ¯æµä¾›ä½ 挑选:{interestsText}。" +msgstr "è¿™é‡Œæœ‰ä¸€äº›åŸºäºŽä½ å…´è¶£æ‰€æŽ¨èçš„ä¿¡æ¯æµä¾›ä½ 挑选:{interestsText}ã€‚å…³æ³¨çš„ä¿¡æ¯æµæ•°é‡æ²¡æœ‰é™åˆ¶ã€‚" #: src/view/com/modals/AddAppPasswords.tsx:153 msgid "Here is your app password." -msgstr "è¿™é‡Œæ˜¯ä½ çš„ App 专用密ç 。" +msgstr "è¿™é‡Œæ˜¯ä½ çš„åº”ç”¨ä¸“ç”¨å¯†ç 。" #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:41 #: src/view/com/modals/ContentFilteringSettings.tsx:251 @@ -1875,7 +1875,7 @@ msgstr "è‹¥ä¸å‹¾é€‰ï¼Œåˆ™é»˜è®¤ä¸ºå…¨å¹´é¾„å‘。" #: src/view/com/modals/ChangePassword.tsx:146 msgid "If you want to change your password, we will send you a code to verify that this is your account." -msgstr "å¦‚æžœä½ æƒ³è¦æ›´æ”¹å¯†ç ,我们将å‘ä½ å‘é€ä¸€ä¸ªç¡®è®¤ç 以验è¯è¿™æ˜¯ä½ 的账户。" +msgstr "å¦‚æžœä½ æƒ³è¦æ›´æ”¹å¯†ç ,我们将å‘ä½ å‘é€ä¸€ä¸ªéªŒè¯ç 以验è¯è¿™æ˜¯ä½ 的账户。" #: src/view/com/util/images/Gallery.tsx:38 msgid "Image" @@ -1908,7 +1908,7 @@ msgstr "输入邀请ç 以继ç»" #: src/view/com/modals/AddAppPasswords.tsx:180 msgid "Input name for app password" -msgstr "输入 App 专用密ç åç§°" +msgstr "输入应用专用密ç åç§°" #: src/view/com/auth/login/SetNewPasswordForm.tsx:162 msgid "Input new password" @@ -1932,7 +1932,7 @@ msgstr "è¾“å…¥æ³¨å†Œæ—¶ä½¿ç”¨çš„ç”¨æˆ·åæˆ–电å邮箱" #: src/view/com/auth/create/Step2.tsx:271 #~ msgid "Input the verification code we have texted to you" -#~ msgstr "输入收到的çŸä¿¡éªŒè¯ç " +#~ msgstr "输入我们å‘é€åˆ°ä½ 手机的çŸä¿¡éªŒè¯ç " #: src/view/com/modals/Waitlist.tsx:90 #~ msgid "Input your email to get on the Bluesky waitlist" @@ -1973,7 +1973,7 @@ msgstr "é‚€è¯·ç æ— æ•ˆï¼Œè¯·æ£€æŸ¥ä½ è¾“å…¥çš„é‚€è¯·ç å¹¶é‡è¯•。" #: src/view/com/modals/InviteCodes.tsx:170 msgid "Invite codes: {0} available" -msgstr "邀请ç :{0} å¯ç”¨" +msgstr "邀请ç :{0} 个å¯ç”¨" #: src/view/shell/Drawer.tsx:645 #~ msgid "Invite codes: {invitesAvailable} available" @@ -1981,7 +1981,7 @@ msgstr "邀请ç :{0} å¯ç”¨" #: src/view/com/modals/InviteCodes.tsx:169 msgid "Invite codes: 1 available" -msgstr "邀请ç :1 å¯ç”¨" +msgstr "邀请ç :1 个å¯ç”¨" #: src/screens/Onboarding/StepFollowingFeed.tsx:64 msgid "It shows posts from the people you follow as they happen." @@ -2054,7 +2054,7 @@ msgstr "了解有关 Bluesky 公开内容的更多详情。" #: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:82 msgid "Leave them all unchecked to see any language." -msgstr "全部ä¸é€‰ä¸ä»¥æŸ¥çœ‹ä»»ä½•è¯è¨€ã€‚" +msgstr "全部留空以查看所有è¯è¨€çš„帖å。" #: src/view/com/modals/LinkWarning.tsx:51 msgid "Leaving Bluesky" @@ -2088,44 +2088,44 @@ msgstr "亮色" #: src/view/com/util/post-ctrls/PostCtrls.tsx:182 msgid "Like" -msgstr "点赞" +msgstr "喜欢" #: src/view/screens/ProfileFeed.tsx:591 msgid "Like this feed" -msgstr "ç‚¹èµžè¿™ä¸ªä¿¡æ¯æµ" +msgstr "å–œæ¬¢è¿™ä¸ªä¿¡æ¯æµ" #: src/Navigation.tsx:199 msgid "Liked by" -msgstr "点赞" +msgstr "喜欢" #: src/view/screens/PostLikedBy.tsx:27 #: src/view/screens/ProfileFeedLikedBy.tsx:27 msgid "Liked By" -msgstr "点赞" +msgstr "喜欢" #: src/view/com/feeds/FeedSourceCard.tsx:279 msgid "Liked by {0} {1}" -msgstr "{0} {1} 点赞" +msgstr "{0} 个 {1} 喜欢" #: src/view/screens/ProfileFeed.tsx:606 msgid "Liked by {likeCount} {0}" -msgstr "{likeCount} {0} 点赞" +msgstr "{likeCount} 个 {0} 喜欢" #: src/view/com/notifications/FeedItem.tsx:170 msgid "liked your custom feed" -msgstr "ç‚¹èµžä½ çš„è‡ªå®šä¹‰ä¿¡æ¯æµ" +msgstr "èµžäº†ä½ çš„è‡ªå®šä¹‰ä¿¡æ¯æµ" #: src/view/com/notifications/FeedItem.tsx:155 msgid "liked your post" -msgstr "ç‚¹èµžä½ çš„å¸–å" +msgstr "èµžäº†ä½ çš„å¸–å" #: src/view/screens/Profile.tsx:183 msgid "Likes" -msgstr "点赞" +msgstr "喜欢" #: src/view/com/post-thread/PostThreadItem.tsx:183 msgid "Likes on this post" -msgstr "点赞这æ¡å¸–å" +msgstr "è¿™æ¡å¸–å的喜欢数" #: src/Navigation.tsx:168 msgid "List" @@ -2157,11 +2157,11 @@ msgstr "列表åç§°" #: src/view/screens/ProfileList.tsx:343 msgid "List unblocked" -msgstr "å–æ¶ˆå±è”½åˆ—表" +msgstr "解除对列表的å±è”½" #: src/view/screens/ProfileList.tsx:302 msgid "List unmuted" -msgstr "å–æ¶ˆéšè—列表" +msgstr "解除对列表的éšè—" #: src/Navigation.tsx:112 #: src/view/screens/Profile.tsx:185 @@ -2216,19 +2216,19 @@ msgstr "登录未列出的账户" #: src/view/com/modals/LinkWarning.tsx:65 msgid "Make sure this is where you intend to go!" -msgstr "请确认ï¼" +msgstr "è¯·ç¡®è®¤ç›®æ ‡é¡µé¢åœ°å€æ˜¯å¦æ£ç¡®ï¼" #: src/components/dialogs/MutedWords.tsx:83 msgid "Manage your muted words and tags" -msgstr "" +msgstr "管ç†ä½ çš„éšè—è¯å’Œè¯é¢˜æ ‡ç¾" #: src/view/com/auth/create/Step2.tsx:118 msgid "May not be longer than 253 characters" -msgstr "" +msgstr "ä¸èƒ½é•¿äºŽ 253 个å—符" #: src/view/com/auth/create/Step2.tsx:109 msgid "May only contain letters and numbers" -msgstr "" +msgstr "åªèƒ½åŒ…å«å—æ¯å’Œæ•°å—" #: src/view/screens/Profile.tsx:182 msgid "Media" @@ -2263,17 +2263,17 @@ msgstr "é™åˆ¶" #: src/view/com/lists/ListCard.tsx:93 #: src/view/com/modals/UserAddRemoveLists.tsx:206 msgid "Moderation list by {0}" -msgstr "é™åˆ¶åˆ—表由 {0} 创建" +msgstr "ç”± {0} 创建的é™åˆ¶åˆ—表" #: src/view/screens/ProfileList.tsx:775 msgid "Moderation list by <0/>" -msgstr "é™åˆ¶åˆ—表由 </0> 创建" +msgstr "ç”± </0> 创建的é™åˆ¶åˆ—表" #: src/view/com/lists/ListCard.tsx:91 #: src/view/com/modals/UserAddRemoveLists.tsx:204 #: src/view/screens/ProfileList.tsx:773 msgid "Moderation list by you" -msgstr "é™åˆ¶åˆ—è¡¨ç”±ä½ åˆ›å»º" +msgstr "ä½ åˆ›å»ºçš„é™åˆ¶åˆ—表" #: src/view/com/modals/CreateOrEditList.tsx:197 msgid "Moderation list created" @@ -2316,19 +2316,19 @@ msgstr "更多选项" #: src/view/screens/PreferencesThreads.tsx:82 msgid "Most-liked replies first" -msgstr "最多点赞优先" +msgstr "优先显示最多喜欢" #: src/view/com/auth/create/Step2.tsx:122 msgid "Must be at least 3 characters" -msgstr "" +msgstr "需è¦è‡³å°‘ 3 个å—符" #: src/components/TagMenu/index.tsx:249 msgid "Mute" -msgstr "" +msgstr "éšè—" #: src/components/TagMenu/index.web.tsx:105 msgid "Mute {truncatedTag}" -msgstr "" +msgstr "éšè— {truncatedTag}" #: src/view/com/profile/ProfileHeader.tsx:327 msgid "Mute Account" @@ -2340,19 +2340,19 @@ msgstr "éšè—账户" #: src/components/TagMenu/index.tsx:209 msgid "Mute all {displayTag} posts" -msgstr "" +msgstr "éšè—所有 {displayTag} 的帖å" #: src/components/TagMenu/index.tsx:211 #~ msgid "Mute all {tag} posts" -#~ msgstr "" +#~ msgstr "éšè—所有 {tag} 的帖å" #: src/components/dialogs/MutedWords.tsx:149 msgid "Mute in tags only" -msgstr "" +msgstr "ä»…éšè—è¯é¢˜æ ‡ç¾" #: src/components/dialogs/MutedWords.tsx:134 msgid "Mute in text & tags" -msgstr "" +msgstr "éšè—文本和è¯é¢˜æ ‡ç¾" #: src/view/screens/ProfileList.tsx:491 msgid "Mute list" @@ -2368,11 +2368,11 @@ msgstr "éšè—这个列表" #: src/components/dialogs/MutedWords.tsx:127 msgid "Mute this word in post text and tags" -msgstr "" +msgstr "åœ¨å¸–åæ–‡æœ¬å’Œè¯é¢˜æ ‡ç¾ä¸éšè—该è¯" #: src/components/dialogs/MutedWords.tsx:142 msgid "Mute this word in tags only" -msgstr "" +msgstr "仅在è¯é¢˜æ ‡ç¾ä¸éšè—该è¯" #: src/view/com/util/forms/PostDropdownBtn.tsx:251 #: src/view/com/util/forms/PostDropdownBtn.tsx:257 @@ -2382,7 +2382,7 @@ msgstr "éšè—讨论串" #: src/view/com/util/forms/PostDropdownBtn.tsx:267 #: src/view/com/util/forms/PostDropdownBtn.tsx:269 msgid "Mute words & tags" -msgstr "" +msgstr "éšè—è¯å’Œè¯é¢˜æ ‡ç¾" #: src/view/com/lists/ListCard.tsx:102 msgid "Muted" @@ -2403,7 +2403,7 @@ msgstr "å·²éšè—的账户将ä¸ä¼šåœ¨ä½ çš„é€šçŸ¥æˆ–æ—¶é—´çº¿ä¸æ˜¾ç¤ºï¼Œè¢«éš #: src/view/screens/Moderation.tsx:100 msgid "Muted words & tags" -msgstr "" +msgstr "å·²éšè—è¯å’Œè¯é¢˜æ ‡ç¾" #: src/view/screens/ProfileList.tsx:277 msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." @@ -2419,7 +2419,7 @@ msgstr "è‡ªå®šä¹‰ä¿¡æ¯æµ" #: src/view/shell/desktop/LeftNav.tsx:65 msgid "My Profile" -msgstr "个人资料" +msgstr "我的个人资料" #: src/view/screens/Settings/index.tsx:582 msgid "My Saved Feeds" @@ -2427,7 +2427,7 @@ msgstr "我ä¿å˜çš„ä¿¡æ¯æµ" #: src/view/com/auth/server-input/index.tsx:118 msgid "my-server.com" -msgstr "" +msgstr "my-server.com" #: src/view/com/modals/AddAppPasswords.tsx:179 #: src/view/com/modals/CreateOrEditList.tsx:290 @@ -2470,7 +2470,7 @@ msgstr "永远ä¸ä¼šå¤±åŽ»å¯¹ä½ çš„å…³æ³¨è€…æˆ–æ•°æ®çš„访问。" #: src/components/dialogs/MutedWords.tsx:293 msgid "Nevermind" -msgstr "" +msgstr "放弃" #: src/view/screens/Lists.tsx:76 msgctxt "action" @@ -2519,7 +2519,7 @@ msgstr "新的用户列表" #: src/view/screens/PreferencesThreads.tsx:79 msgid "Newest replies first" -msgstr "最新回å¤ä¼˜å…ˆ" +msgstr "优先显示最新回å¤" #: src/screens/Onboarding/index.tsx:23 msgid "News" @@ -2535,12 +2535,12 @@ msgstr "æ–°é—»" #: src/view/com/modals/ChangePassword.tsx:251 #: src/view/com/modals/ChangePassword.tsx:253 msgid "Next" -msgstr "下一个" +msgstr "下一æ¥" #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103 msgctxt "action" msgid "Next" -msgstr "下一个" +msgstr "下一æ¥" #: src/view/com/lightbox/Lightbox.web.tsx:149 msgid "Next image" @@ -2566,7 +2566,7 @@ msgstr "ä¸å†å…³æ³¨ {0}" #: src/view/com/notifications/Feed.tsx:109 msgid "No notifications yet!" -msgstr "没有通知ï¼" +msgstr "还没有通知ï¼" #: src/view/com/composer/text-input/mobile/Autocomplete.tsx:97 #: src/view/com/composer/text-input/web/Autocomplete.tsx:191 @@ -2575,7 +2575,7 @@ msgstr "没有结果" #: src/components/Lists.tsx:192 msgid "No results found" -msgstr "" +msgstr "未找到结果" #: src/view/screens/Feeds.tsx:495 msgid "No results found for \"{query}\"" @@ -2607,7 +2607,7 @@ msgstr "未找到" #: src/view/com/modals/VerifyEmail.tsx:246 #: src/view/com/modals/VerifyEmail.tsx:252 msgid "Not right now" -msgstr "䏿˜¯çŽ°åœ¨" +msgstr "暂时ä¸éœ€è¦" #: src/view/screens/Moderation.tsx:252 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." @@ -2641,7 +2641,7 @@ msgstr "好的" #: src/view/screens/PreferencesThreads.tsx:78 msgid "Oldest replies first" -msgstr "最旧的回å¤ä¼˜å…ˆ" +msgstr "优先显示最旧的回å¤" #: src/view/screens/Settings/index.tsx:234 msgid "Onboarding reset" @@ -2657,7 +2657,7 @@ msgstr "åªæœ‰ {0} å¯ä»¥å›žå¤ã€‚" #: src/components/Lists.tsx:82 msgid "Oops, something went wrong!" -msgstr "" +msgstr "糟糕,å‘生了一些错误ï¼" #: src/components/Lists.tsx:188 #: src/view/screens/AppPasswords.tsx:65 @@ -2667,16 +2667,16 @@ msgstr "Oopsï¼" #: src/screens/Onboarding/StepFinished.tsx:115 msgid "Open" -msgstr "打开" +msgstr "å¼€å¯" #: src/view/screens/Moderation.tsx:75 msgid "Open content filtering settings" -msgstr "" +msgstr "打开内容过滤设置" #: src/view/com/composer/Composer.tsx:477 #: src/view/com/composer/Composer.tsx:478 msgid "Open emoji picker" -msgstr "打开 emoji 选择器" +msgstr "打开表情符å·é€‰æ‹©å™¨" #: src/view/screens/Settings/index.tsx:712 msgid "Open links with in-app browser" @@ -2684,116 +2684,116 @@ msgstr "在内置æµè§ˆå™¨ä¸æ‰“开链接" #: src/view/screens/Moderation.tsx:92 msgid "Open muted words settings" -msgstr "" +msgstr "打开éšè—è¯è®¾ç½®" #: src/view/com/home/HomeHeaderLayoutMobile.tsx:50 msgid "Open navigation" -msgstr "打开导航" +msgstr "å¼€å¯å¯¼èˆª" #: src/view/com/util/forms/PostDropdownBtn.tsx:175 msgid "Open post options menu" -msgstr "" +msgstr "打开帖å选项èœå•" #: src/view/screens/Settings/index.tsx:804 msgid "Open storybook page" -msgstr "打开故事书界é¢" +msgstr "å¼€å¯ Storybook 界é¢" #: src/view/com/util/forms/DropdownButton.tsx:154 msgid "Opens {numItems} options" -msgstr "打开 {numItems} 个选项" +msgstr "å¼€å¯ {numItems} 个选项" #: src/view/screens/Log.tsx:54 msgid "Opens additional details for a debug entry" -msgstr "æ‰“å¼€è°ƒè¯•è®°å½•çš„é™„åŠ è¯¦ç»†ä¿¡æ¯" +msgstr "å¼€å¯è°ƒè¯•记录的é¢å¤–详细信æ¯" #: src/view/com/notifications/FeedItem.tsx:349 msgid "Opens an expanded list of users in this notification" -msgstr "打开æ¤é€šçŸ¥ä¸çš„æ‰©å±•用户列表" +msgstr "展开æ¤é€šçŸ¥ä¸çš„æ‰©å±•用户列表" #: src/view/com/composer/photos/OpenCameraBtn.tsx:61 msgid "Opens camera on device" -msgstr "打开设备相机" +msgstr "å¼€å¯è®¾å¤‡ç›¸æœº" #: src/view/com/composer/Prompt.tsx:25 msgid "Opens composer" -msgstr "打开编辑器" +msgstr "å¼€å¯ç¼–辑器" #: src/view/screens/Settings/index.tsx:595 msgid "Opens configurable language settings" -msgstr "打开å¯é…置的è¯è¨€è®¾ç½®" +msgstr "å¼€å¯å¯é…置的è¯è¨€è®¾ç½®" #: src/view/com/composer/photos/SelectPhotoBtn.tsx:44 msgid "Opens device photo gallery" -msgstr "打开设备相册" +msgstr "å¼€å¯è®¾å¤‡ç›¸å†Œ" #: src/view/com/profile/ProfileHeader.tsx:420 msgid "Opens editor for profile display name, avatar, background image, and description" -msgstr "打开个人资料(如åç§°ã€å¤´åƒã€èƒŒæ™¯å›¾ç‰‡ã€æè¿°ç‰ï¼‰ç¼–辑器" +msgstr "å¼€å¯ä¸ªäººèµ„料(如åç§°ã€å¤´åƒã€èƒŒæ™¯å›¾ç‰‡ã€æè¿°ç‰ï¼‰ç¼–辑器" #: src/view/screens/Settings/index.tsx:649 msgid "Opens external embeds settings" -msgstr "打开外部嵌入设置" +msgstr "å¼€å¯å¤–部嵌入设置" #: src/view/com/profile/ProfileHeader.tsx:575 msgid "Opens followers list" -msgstr "打开关注者列表" +msgstr "å¼€å¯å…³æ³¨è€…列表" #: src/view/com/profile/ProfileHeader.tsx:594 msgid "Opens following list" -msgstr "打开æ£åœ¨å…³æ³¨åˆ—表" +msgstr "开坿£åœ¨å…³æ³¨åˆ—表" #: src/view/screens/Settings.tsx:412 #~ msgid "Opens invite code list" -#~ msgstr "打开邀请ç 列表" +#~ msgstr "å¼€å¯é‚€è¯·ç 列表" #: src/view/com/modals/InviteCodes.tsx:172 msgid "Opens list of invite codes" -msgstr "打开邀请ç 列表" +msgstr "å¼€å¯é‚€è¯·ç 列表" #: src/view/screens/Settings/index.tsx:774 msgid "Opens modal for account deletion confirmation. Requires email code." -msgstr "æ‰“å¼€ç”¨æˆ·åˆ é™¤ç¡®è®¤ç•Œé¢ï¼Œéœ€è¦ç”µå邮箱接收验è¯ç 。" +msgstr "å¼€å¯ç”¨æˆ·åˆ 除确认界é¢ï¼Œéœ€è¦ç”µå邮箱接收验è¯ç 。" #: src/view/com/modals/ChangeHandle.tsx:281 msgid "Opens modal for using custom domain" -msgstr "打开使用自定义域å的模å¼" +msgstr "å¼€å¯ä½¿ç”¨è‡ªå®šä¹‰åŸŸå的模å¼" #: src/view/screens/Settings/index.tsx:620 msgid "Opens moderation settings" -msgstr "打开é™åˆ¶è®¾ç½®" +msgstr "å¼€å¯é™åˆ¶è®¾ç½®" #: src/view/com/auth/login/LoginForm.tsx:239 msgid "Opens password reset form" -msgstr "打开密ç é‡ç½®ç”³è¯·" +msgstr "å¼€å¯å¯†ç é‡ç½®ç”³è¯·" #: src/view/com/home/HomeHeaderLayout.web.tsx:63 #: src/view/screens/Feeds.tsx:356 msgid "Opens screen to edit Saved Feeds" -msgstr "打开用于编辑已ä¿å˜ä¿¡æ¯æµçš„界é¢" +msgstr "å¼€å¯ç”¨äºŽç¼–辑已ä¿å˜ä¿¡æ¯æµçš„界é¢" #: src/view/screens/Settings/index.tsx:576 msgid "Opens screen with all saved feeds" -msgstr "æ‰“å¼€åŒ…å«æ‰€æœ‰å·²ä¿å˜ä¿¡æ¯æµçš„界é¢" +msgstr "å¼€å¯åŒ…嫿‰€æœ‰å·²ä¿å˜ä¿¡æ¯æµçš„界é¢" #: src/view/screens/Settings/index.tsx:676 msgid "Opens the app password settings page" -msgstr "打开 App 专用密ç 设置页" +msgstr "å¼€å¯åº”用专用密ç 设置页" #: src/view/screens/Settings/index.tsx:535 msgid "Opens the home feed preferences" -msgstr "æ‰“å¼€ä¸»é¡µä¿¡æ¯æµé¦–选项" +msgstr "å¼€å¯ä¸»é¡µä¿¡æ¯æµé¦–选项" #: src/view/screens/Settings/index.tsx:805 msgid "Opens the storybook page" -msgstr "打开故事书界é¢" +msgstr "å¼€å¯ Storybook 界é¢" #: src/view/screens/Settings/index.tsx:793 msgid "Opens the system log page" -msgstr "打开系统日志界é¢" +msgstr "å¼€å¯ç³»ç»Ÿæ—¥å¿—界é¢" #: src/view/screens/Settings/index.tsx:556 msgid "Opens the threads preferences" -msgstr "打开讨论串首选项" +msgstr "å¼€å¯è®¨è®ºä¸²é¦–选项" #: src/view/com/util/forms/DropdownButton.tsx:280 msgid "Option {0} of {numItems}" @@ -2842,11 +2842,11 @@ msgstr "密ç 已更新ï¼" #: src/Navigation.tsx:162 msgid "People followed by @{0}" -msgstr "被这些人所关注 @{0}" +msgstr "@{0} 关注的人" #: src/Navigation.tsx:155 msgid "People following @{0}" -msgstr "æ£åœ¨å…³æ³¨ @{0}" +msgstr "关注 @{0} 的人" #: src/view/com/lightbox/Lightbox.tsx:66 msgid "Permission to access camera roll is required." @@ -2900,15 +2900,15 @@ msgstr "è¯·è®¾ç½®ä½ çš„å¯†ç 。" #: src/view/com/auth/create/state.ts:131 msgid "Please complete the verification captcha." -msgstr "" +msgstr "è¯·å®Œæˆ Captcha 验è¯" #: src/view/com/modals/ChangeEmail.tsx:67 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 "更改å‰è¯·å…ˆç¡®è®¤ä½ 的电åé‚®ç®±ã€‚è¿™æ˜¯æ·»åŠ ç”µåé‚®ç®±æ›´æ–°å·¥å…·çš„ä¸´æ—¶è¦æ±‚,æ¤é™åˆ¶å°†å¾ˆå¿«è¢«ç§»é™¤ã€‚" +msgstr "更改å‰è¯·å…ˆç¡®è®¤ä½ 的电å邮箱。这是新增电åé‚®ç®±æ›´æ–°å·¥å…·çš„ä¸´æ—¶è¦æ±‚,æ¤é™åˆ¶å°†å¾ˆå¿«è¢«ç§»é™¤ã€‚" #: src/view/com/modals/AddAppPasswords.tsx:90 msgid "Please enter a name for your app password. All spaces is not allowed." -msgstr "请输入 App 专用密ç çš„å称,ä¸å…è®¸ä½¿ç”¨ç©ºæ ¼ã€‚" +msgstr "请输入应用专用密ç çš„å称,ä¸å…è®¸ä½¿ç”¨ç©ºæ ¼ã€‚" #: src/view/com/auth/create/Step2.tsx:206 #~ msgid "Please enter a phone number that can receive SMS text messages." @@ -2916,11 +2916,11 @@ msgstr "请输入 App 专用密ç çš„å称,ä¸å…è®¸ä½¿ç”¨ç©ºæ ¼ã€‚" #: src/view/com/modals/AddAppPasswords.tsx:145 msgid "Please enter a unique name for this App Password or use our randomly generated one." -msgstr "è¯·è¾“å…¥æ¤ App 专用密ç 的唯一å称,或使用我们æä¾›çš„éšæœºç”Ÿæˆå称。" +msgstr "请输入æ¤åº”用专用密ç 的唯一å称,或使用我们æä¾›çš„éšæœºç”Ÿæˆå称。" #: src/components/dialogs/MutedWords.tsx:68 msgid "Please enter a valid word, tag, or phrase to mute" -msgstr "" +msgstr "请输入一个有效的è¯ã€è¯é¢˜æ ‡ç¾æˆ–çŸè¯" #: src/view/com/auth/create/state.ts:170 #~ msgid "Please enter the code you received by SMS." @@ -2949,7 +2949,7 @@ msgstr "请验è¯ä½ 的电å邮箱" #: src/view/com/composer/Composer.tsx:222 msgid "Please wait for your link card to finish loading" -msgstr "请ç‰å¾…ä½ çš„é“¾æŽ¥å¡ç‰‡åŠ è½½å®Œæˆ" +msgstr "请ç‰å¾…ä½ çš„é“¾æŽ¥å¡ç‰‡åŠ è½½å®Œæ¯•" #: src/screens/Onboarding/index.tsx:37 msgid "Politics" @@ -2972,13 +2972,13 @@ msgstr "å‘布" #: src/view/com/post-thread/PostThreadItem.tsx:175 msgid "Post by {0}" -msgstr "å‘布者 {0}" +msgstr "{0} 的帖å" #: src/Navigation.tsx:174 #: src/Navigation.tsx:181 #: src/Navigation.tsx:188 msgid "Post by @{0}" -msgstr "å‘布者 @{0}" +msgstr "@{0} 的帖å" #: src/view/com/util/forms/PostDropdownBtn.tsx:108 msgid "Post deleted" @@ -3002,7 +3002,7 @@ msgstr "æ— æ³•æ‰¾åˆ°å¸–å" #: src/components/TagMenu/index.tsx:253 msgid "posts" -msgstr "" +msgstr "帖å" #: src/view/screens/Profile.tsx:180 msgid "Posts" @@ -3010,11 +3010,11 @@ msgstr "帖å" #: src/components/dialogs/MutedWords.tsx:90 msgid "Posts can be muted based on their text, their tags, or both." -msgstr "" +msgstr "帖åå¯ä»¥æ ¹æ®å…¶æ–‡æœ¬ã€è¯é¢˜æ ‡ç¾æˆ–两者æ¥éšè—。" #: src/view/com/posts/FeedErrorMessage.tsx:64 msgid "Posts hidden" -msgstr "å·²éšè—帖å" +msgstr "帖åå·²éšè—" #: src/view/com/modals/LinkWarning.tsx:46 msgid "Potentially Misleading Link" @@ -3030,7 +3030,7 @@ msgstr "首选è¯è¨€" #: src/view/screens/PreferencesThreads.tsx:97 msgid "Prioritize Your Follows" -msgstr "关注者优先" +msgstr "优先显示关注者" #: src/view/screens/Settings/index.tsx:632 #: src/view/shell/desktop/RightNav.tsx:72 @@ -3054,11 +3054,11 @@ msgstr "处ç†ä¸..." #: src/view/shell/Drawer.tsx:546 #: src/view/shell/Drawer.tsx:547 msgid "Profile" -msgstr "资料" +msgstr "个人资料" #: src/view/com/modals/EditProfile.tsx:128 msgid "Profile updated" -msgstr "资料已更新" +msgstr "个人资料已更新" #: src/view/screens/Settings/index.tsx:949 msgid "Protect your account by verifying your email." @@ -3100,7 +3100,7 @@ msgstr "引用帖å" #: src/view/screens/PreferencesThreads.tsx:86 msgid "Random (aka \"Poster's Roulette\")" -msgstr "ä»¥éšæœºé¡ºåºæ˜¾ç¤º (åˆå试试手气)" +msgstr "éšæœºæ˜¾ç¤º (手气ä¸é”™)" #: src/view/com/modals/EditImage.tsx:236 msgid "Ratios" @@ -3121,7 +3121,7 @@ msgstr "推è的用户" #: src/view/com/util/UserAvatar.tsx:285 #: src/view/com/util/UserBanner.tsx:91 msgid "Remove" -msgstr "åˆ é™¤" +msgstr "移除" #: src/view/com/feeds/FeedSourceCard.tsx:108 msgid "Remove {0} from my feeds?" @@ -3154,7 +3154,7 @@ msgstr "åˆ é™¤å›¾ç‰‡é¢„è§ˆ" #: src/components/dialogs/MutedWords.tsx:343 msgid "Remove mute word from your list" -msgstr "" +msgstr "ä»Žä½ çš„éšè—è¯åˆ—表ä¸åˆ 除" #: src/view/com/modals/Repost.tsx:47 msgid "Remove repost" @@ -3280,20 +3280,20 @@ msgstr "确认ç " #: src/view/screens/Settings/index.tsx:456 msgid "Require alt text before posting" -msgstr "è¦æ±‚å‘å¸ƒå‰æä¾›æ›¿ä»£æ–‡æœ¬" +msgstr "å‘布时检查媒体是å¦å˜åœ¨æ›¿ä»£æ–‡æœ¬" #: src/view/com/auth/create/Step1.tsx:146 msgid "Required for this provider" -msgstr "应æä¾›å•†è¦æ±‚" +msgstr "æœåŠ¡æä¾›è€…è¦æ±‚" #: src/view/com/auth/login/SetNewPasswordForm.tsx:124 #: src/view/com/auth/login/SetNewPasswordForm.tsx:136 msgid "Reset code" -msgstr "é‡ç½®ç " +msgstr "确认ç " #: src/view/com/modals/ChangePassword.tsx:190 msgid "Reset Code" -msgstr "é‡ç½®ä»£ç " +msgstr "确认ç " #: src/view/screens/Settings/index.tsx:824 msgid "Reset onboarding" @@ -3380,7 +3380,7 @@ msgstr "ä¿å˜æ›´æ”¹" #: src/view/com/modals/ChangeHandle.tsx:170 msgid "Save handle change" -msgstr "ä¿å˜æ–°çš„用户识别符" +msgstr "ä¿å˜ç”¨æˆ·è¯†åˆ«ç¬¦æ›´æ”¹" #: src/view/com/modals/crop-image/CropImage.web.tsx:144 msgid "Save image crop" @@ -3430,19 +3430,19 @@ msgstr "æœç´¢ \"{query}\"" #: src/components/TagMenu/index.tsx:145 msgid "Search for all posts by @{authorHandle} with tag {displayTag}" -msgstr "" +msgstr "æœç´¢ @{authorHandle} 带有 {displayTag} 的所有帖å" #: src/components/TagMenu/index.tsx:145 #~ msgid "Search for all posts by @{authorHandle} with tag {tag}" -#~ msgstr "" +#~ msgstr "æœç´¢ @{authorHandle} 带有 {tag} 的所有帖å" #: src/components/TagMenu/index.tsx:94 msgid "Search for all posts with tag {displayTag}" -msgstr "" +msgstr "æœç´¢æ‰€æœ‰å¸¦æœ‰ {displayTag} 的帖å" #: src/components/TagMenu/index.tsx:90 #~ msgid "Search for all posts with tag {tag}" -#~ msgstr "" +#~ msgstr "æœç´¢æ‰€æœ‰å¸¦æœ‰ {tag} 的帖å" #: src/view/com/auth/LoggedOut.tsx:104 #: src/view/com/auth/LoggedOut.tsx:105 @@ -3456,27 +3456,27 @@ msgstr "所需的安全æ¥éª¤" #: src/components/TagMenu/index.web.tsx:66 msgid "See {truncatedTag} posts" -msgstr "" +msgstr "查看 {truncatedTag} 的帖å" #: src/components/TagMenu/index.web.tsx:83 msgid "See {truncatedTag} posts by user" -msgstr "" +msgstr "按用户查看 {truncatedTag} 的帖å" #: src/components/TagMenu/index.tsx:128 msgid "See <0>{displayTag}</0> posts" -msgstr "" +msgstr "查看 <0>{displayTag}</0> 的帖å" #: src/components/TagMenu/index.tsx:187 msgid "See <0>{displayTag}</0> posts by this user" -msgstr "" +msgstr "查看该用户 <0>{displayTag}</0> 的帖å" #: src/components/TagMenu/index.tsx:128 #~ msgid "See <0>{tag}</0> posts" -#~ msgstr "" +#~ msgstr "查看 <0>{tag}</0> 的帖å" #: src/components/TagMenu/index.tsx:189 #~ msgid "See <0>{tag}</0> posts by this user" -#~ msgstr "" +#~ msgstr "查看该用户 <0>{tag}</0> 的帖å" #: src/view/screens/SavedFeeds.tsx:163 msgid "See this guide" @@ -3496,11 +3496,11 @@ msgstr "选择 {item}" #: src/view/com/auth/login/Login.tsx:117 msgid "Select from an existing account" -msgstr "选择已å˜åœ¨çš„账户" +msgstr "从现有账户ä¸é€‰æ‹©" #: src/view/com/util/Selector.tsx:107 msgid "Select option {i} of {numItems}" -msgstr "从 {i} 项ä¸é€‰æ‹© {numItems} 项" +msgstr "选择 {numItems} 项ä¸çš„第 {i} 项" #: src/view/com/auth/create/Step1.tsx:96 #: src/view/com/auth/login/LoginForm.tsx:150 @@ -3513,7 +3513,7 @@ msgstr "选择以下一些账户进行关注" #: src/view/com/auth/server-input/index.tsx:82 msgid "Select the service that hosts your data." -msgstr "" +msgstr "é€‰æ‹©æ‰˜ç®¡ä½ æ•°æ®çš„æœåŠ¡å™¨ã€‚" #: src/screens/Onboarding/StepTopicalFeeds.tsx:96 msgid "Select topical feeds to follow from the list below" @@ -3576,11 +3576,11 @@ msgstr "æäº¤ä¸¾æŠ¥" #: src/view/com/modals/DeleteAccount.tsx:133 msgid "Sends email with confirmation code for account deletion" -msgstr "å‘é€åŒ…å«è´¦æˆ·åˆ 除确认ç 的电å邮件" +msgstr "å‘é€åŒ…å«è´¦æˆ·åˆ 除验è¯ç 的电å邮件" #: src/view/com/auth/server-input/index.tsx:110 msgid "Server address" -msgstr "" +msgstr "æœåŠ¡å™¨åœ°å€" #: src/view/com/modals/ContentFilteringSettings.tsx:311 msgid "Set {value} for {labelGroup} content moderation policy" @@ -3642,7 +3642,7 @@ msgstr "å¯ç”¨æ¤è®¾ç½®é¡¹ä»¥åœ¨åˆ†å±‚è§†å›¾ä¸æ˜¾ç¤ºå›žå¤ã€‚这是一个实验 #: src/view/screens/PreferencesFollowingFeed.tsx:261 msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your Following feed. This is an experimental feature." -msgstr "" +msgstr "å¯ç”¨æ¤è®¾ç½®é¡¹ä»¥åœ¨å…³æ³¨ä¿¡æ¯æµä¸æ˜¾ç¤ºå·²ä¿å˜ä¿¡æ¯æµçš„æ ·ä¾‹ã€‚这是一个实验性功能。" #: src/screens/Onboarding/Layout.tsx:50 msgid "Set up your account" @@ -3762,7 +3762,7 @@ msgstr "åœ¨å…³æ³¨ä¿¡æ¯æµä¸æ˜¾ç¤ºå›žå¤" #: src/view/screens/PreferencesFollowingFeed.tsx:70 msgid "Show replies with at least {value} {0}" -msgstr "æ˜¾ç¤ºè‡³å°‘åŒ…å« {value} {0} 的回å¤" +msgstr "æ˜¾ç¤ºè‡³å°‘åŒ…å« {value} 个 {0} 的回å¤" #: src/view/screens/PreferencesFollowingFeed.tsx:188 msgid "Show Reposts" @@ -3886,7 +3886,7 @@ msgstr "程åºå¼€å‘" #: src/components/Lists.tsx:203 msgid "Something went wrong!" -msgstr "" +msgstr "出了点问题ï¼" #: src/view/com/modals/Waitlist.tsx:51 #~ msgid "Something went wrong. Check your email and try again." @@ -3894,7 +3894,7 @@ msgstr "" #: src/App.native.tsx:66 msgid "Sorry! Your session expired. Please log in again." -msgstr "很抱æ‰ï¼Œä½ 的会è¯å·²è¿‡æœŸï¼Œè¯·é‡æ–°ç™»å½•。" +msgstr "很抱æ‰ï¼Œä½ 的登录会è¯å·²è¿‡æœŸï¼Œè¯·é‡æ–°ç™»å½•。" #: src/view/screens/PreferencesThreads.tsx:69 msgid "Sort Replies" @@ -3926,12 +3926,12 @@ msgstr "第 {0} æ¥ï¼Œå…± {numSteps} æ¥" #: src/view/screens/Settings/index.tsx:274 msgid "Storage cleared, you need to restart the app now." -msgstr "已清除å˜å‚¨ï¼Œè¯·ç«‹å³é‡å¯ App。" +msgstr "已清除å˜å‚¨ï¼Œè¯·ç«‹å³é‡å¯åº”用。" #: src/Navigation.tsx:204 #: src/view/screens/Settings/index.tsx:807 msgid "Storybook" -msgstr "故事书" +msgstr "Storybook" #: src/view/com/modals/AppealLabel.tsx:101 msgid "Submit" @@ -3996,15 +3996,15 @@ msgstr "系统日志" #: src/components/dialogs/MutedWords.tsx:337 msgid "tag" -msgstr "" +msgstr "è¯é¢˜æ ‡ç¾" #: src/components/TagMenu/index.tsx:78 msgid "Tag menu: {displayTag}" -msgstr "" +msgstr "è¯é¢˜æ ‡ç¾èœå•:{displayTag}" #: src/components/TagMenu/index.tsx:74 #~ msgid "Tag menu: {tag}" -#~ msgstr "" +#~ msgstr "è¯é¢˜æ ‡ç¾èœå•:{tag}" #: src/view/com/modals/crop-image/CropImage.web.tsx:112 msgid "Tall" @@ -4031,16 +4031,16 @@ msgstr "æœåŠ¡æ¡æ¬¾" #: src/components/dialogs/MutedWords.tsx:337 msgid "text" -msgstr "" +msgstr "文本" #: src/view/com/modals/AppealLabel.tsx:70 #: src/view/com/modals/report/InputIssueDetails.tsx:51 msgid "Text input field" -msgstr "æ–‡æœ¬è¾“å…¥å—æ®µ" +msgstr "文本输入框" #: src/view/com/auth/create/CreateAccount.tsx:94 msgid "That handle is already taken." -msgstr "" +msgstr "该用户识别符已被å 用" #: src/view/com/profile/ProfileHeader.tsx:263 msgid "The account will be able to interact with you after unblocking." @@ -4060,7 +4060,7 @@ msgstr "以下æ¥éª¤å°†å¸®åŠ©å®šåˆ¶ä½ çš„ Bluesky 体验。" #: src/view/com/post-thread/PostThread.tsx:517 msgid "The post may have been deleted." -msgstr "æ¤å¸–åä¼¼ä¹Žå·²è¢«åˆ é™¤ã€‚" +msgstr "æ¤å¸–åå¯èƒ½å·²è¢«åˆ 除。" #: src/view/screens/PrivacyPolicy.tsx:33 msgid "The Privacy Policy has been moved to <0/>" @@ -4068,7 +4068,7 @@ 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}与我们è”系。" +msgstr "支æŒè¡¨å•å·²è¢«ç§»é™¤ã€‚å¦‚æžœä½ éœ€è¦å¸®åŠ©ï¼Œè¯·ç‚¹å‡»<0/>或访问{HELP_DESK_URL}与我们è”系。" #: src/view/screens/TermsOfService.tsx:33 msgid "The Terms of Service have been moved to" @@ -4130,7 +4130,7 @@ msgstr "与æœåŠ¡å™¨åŒæ¥é¦–选项时出现问题" #: src/view/screens/AppPasswords.tsx:66 msgid "There was an issue with fetching your app passwords" -msgstr "èŽ·å– App ä¸“ç”¨å¯†ç æ—¶å‡ºçŽ°é—®é¢˜" +msgstr "获å–åº”ç”¨ä¸“ç”¨å¯†ç æ—¶å‡ºçŽ°é—®é¢˜" #: src/view/com/post-thread/PostThreadFollowBtn.tsx:93 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:105 @@ -4168,7 +4168,7 @@ msgstr "è¿™é‡Œæ˜¯ä¸€äº›å—æ¬¢è¿Žçš„è´¦å·ï¼Œä½ å¯èƒ½ä¼šå–œæ¬¢ï¼š" #: src/view/com/util/moderation/ScreenHider.tsx:88 msgid "This {screenDescription} has been flagged:" -msgstr "这个 {screenDescription} å·²è¢«æ ‡è®°ï¼š" +msgstr "{screenDescription} å·²è¢«æ ‡è®°ï¼š" #: src/view/com/util/moderation/ScreenHider.tsx:83 msgid "This account has requested that users sign in to view their profile." @@ -4188,7 +4188,7 @@ msgstr "没有 Bluesky è´¦æˆ·ï¼Œæ— æ³•æŸ¥çœ‹æ¤å†…容。" #: src/view/screens/Settings/ExportCarDialog.tsx:75 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost.</0>" -msgstr "" +msgstr "该功能æ£åœ¨æµ‹è¯•ã€‚ä½ å¯ä»¥åœ¨<0>这篇åšå®¢æ–‡ç« </0>ä¸èŽ·å¾—å…³äºŽå¯¼å‡ºæ•°æ®çš„æ›´å¤šä¿¡æ¯ã€‚" #: src/view/com/posts/FeedErrorMessage.tsx:114 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." @@ -4206,7 +4206,7 @@ msgstr "è¯¥ä¿¡æ¯æµä¸ºç©ºï¼ä½ 或许需è¦å…ˆå…³æ³¨æ›´å¤šçš„äººæˆ–æ£€æŸ¥ä½ çš„ #: src/view/com/modals/BirthDateSettings.tsx:61 msgid "This information is not shared with other users." -msgstr "æ¤ä¿¡æ¯ä¸ä¸Žå…¶ä»–用户共享。" +msgstr "æ¤ä¿¡æ¯ä¸ä¼šåˆ†äº«ç»™å…¶ä»–用户。" #: src/view/com/modals/VerifyEmail.tsx:119 msgid "This is important in case you ever need to change your email or reset your password." @@ -4226,7 +4226,7 @@ msgstr "该å称已被使用" #: src/view/com/post-thread/PostThreadItem.tsx:125 msgid "This post has been deleted." -msgstr "æ¤å¸–å·²è¢«åˆ é™¤ã€‚" +msgstr "æ¤å¸–åå·²è¢«åˆ é™¤ã€‚" #: src/view/com/modals/ModerationDetails.tsx:62 msgid "This user has blocked you. You cannot view their content." @@ -4250,7 +4250,7 @@ msgstr "æ¤è¦å‘Šä»…适用于附带媒体的帖å。" #: src/components/dialogs/MutedWords.tsx:285 msgid "This will delete {0} from your muted words. You can always add it back later." -msgstr "" +msgstr "è¿™å°†ä»Žä½ çš„éšè—è¯ä¸åˆ 除 {0}ã€‚ä½ éšæ—¶å¯ä»¥é‡æ–°æ·»åŠ ã€‚" #: src/view/com/util/forms/PostDropdownBtn.tsx:282 msgid "This will hide this post from your feeds." @@ -4263,7 +4263,7 @@ msgstr "讨论串首选项" #: src/view/screens/PreferencesThreads.tsx:119 msgid "Threaded Mode" -msgstr "分层模å¼" +msgstr "讨论串模å¼" #: src/Navigation.tsx:257 msgid "Threads Preferences" @@ -4271,11 +4271,11 @@ msgstr "讨论串首选项" #: src/components/dialogs/MutedWords.tsx:113 msgid "Toggle between muted word options." -msgstr "" +msgstr "在éšè—è¯é€‰é¡¹ä¹‹é—´åˆ‡æ¢ã€‚" #: src/view/com/util/forms/DropdownButton.tsx:246 msgid "Toggle dropdown" -msgstr "切æ¢ä¸‹æ‹‰èœå•" +msgstr "切æ¢ä¸‹æ‹‰å¼èœå•" #: src/view/com/modals/EditImage.tsx:271 msgid "Transformations" @@ -4329,16 +4329,16 @@ msgstr "å–æ¶ˆå±è”½" #: src/view/com/util/post-ctrls/RepostButton.tsx:60 #: src/view/com/util/post-ctrls/RepostButton.web.tsx:48 msgid "Undo repost" -msgstr "撤销转å‘" +msgstr "å–æ¶ˆè½¬å‘" #: src/view/com/profile/FollowButton.tsx:55 msgctxt "action" msgid "Unfollow" -msgstr "å–å…³" +msgstr "å–æ¶ˆå…³æ³¨" #: src/view/com/profile/ProfileHeader.tsx:485 msgid "Unfollow {0}" -msgstr "å–å…³ {0}" +msgstr "å–æ¶ˆå…³æ³¨ {0}" #: src/view/com/auth/create/state.ts:262 msgid "Unfortunately, you do not meet the requirements to create an account." @@ -4355,7 +4355,7 @@ msgstr "å–æ¶ˆéšè—" #: src/components/TagMenu/index.web.tsx:104 msgid "Unmute {truncatedTag}" -msgstr "" +msgstr "å–æ¶ˆéšè— {truncatedTag}" #: src/view/com/profile/ProfileHeader.tsx:326 msgid "Unmute Account" @@ -4363,7 +4363,7 @@ msgstr "å–æ¶ˆéšè—账户" #: src/components/TagMenu/index.tsx:208 msgid "Unmute all {displayTag} posts" -msgstr "" +msgstr "å–æ¶ˆéšè—所有 {displayTag} 帖å" #: src/components/TagMenu/index.tsx:210 #~ msgid "Unmute all {tag} posts" @@ -4405,7 +4405,7 @@ msgstr "å°†æ–‡æœ¬æ–‡ä»¶ä¸Šä¼ è‡³ï¼š" #: src/view/screens/AppPasswords.tsx:195 msgid "Use app passwords to login to other Bluesky clients without giving full access to your account or password." -msgstr "使用应用程åºå¯†ç 登录到其他 Bluesky å®¢æˆ·ç«¯ï¼Œè€Œæ— éœ€å¯¹å…¶æŽˆäºˆä½ è´¦æˆ·æˆ–å¯†ç 的完全访问æƒé™ã€‚" +msgstr "使用应用专用密ç 登录到其他 Bluesky å®¢æˆ·ç«¯ï¼Œè€Œæ— éœ€å¯¹å…¶æŽˆäºˆä½ è´¦æˆ·æˆ–å¯†ç 的完全访问æƒé™ã€‚" #: src/view/com/modals/ChangeHandle.tsx:515 msgid "Use default provider" @@ -4520,7 +4520,7 @@ msgstr "验è¯ä½ 的邮箱" #: src/screens/Onboarding/index.tsx:42 msgid "Video Games" -msgstr "视频游æˆ" +msgstr "ç”µåæ¸¸æˆ" #: src/view/com/profile/ProfileHeader.tsx:662 msgid "View {0}'s avatar" @@ -4536,7 +4536,7 @@ msgstr "查看整个讨论串" #: src/view/com/posts/FeedErrorMessage.tsx:172 msgid "View profile" -msgstr "查看资料" +msgstr "查看个人资料" #: src/view/com/profile/ProfileSubpageHeader.tsx:128 msgid "View the avatar" @@ -4557,7 +4557,7 @@ msgstr "æˆ‘ä»¬è®¤ä¸ºè¿˜ä½ ä¼šå–œæ¬¢ Skygaze 所维护的 \"For You\":" #: src/screens/Hashtag.tsx:132 msgid "We couldn't find any results for that hashtag." -msgstr "" +msgstr "找ä¸åˆ°ä»»ä½•与该è¯é¢˜æ ‡ç¾ç›¸å…³çš„结果。" #: src/screens/Deactivated.tsx:133 msgid "We estimate {estimatedTime} until your account is ready." @@ -4573,7 +4573,7 @@ msgstr "我们已ç»çœ‹å®Œäº†ä½ 关注的帖å。这是æ¥è‡ª <0/> 的最新消 #: src/components/dialogs/MutedWords.tsx:204 msgid "We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." -msgstr "" +msgstr "ä¸å»ºè®®æ‚¨ä½¿ç”¨ä¼šå‡ºçŽ°åœ¨è®¸å¤šå¸–åä¸çš„常è§è¯æ±‡ï¼Œè¿™å¯èƒ½å¯¼è‡´ä½ 的时间线上没有帖å坿˜¾ç¤ºã€‚" #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:124 msgid "We recommend our \"Discover\" feed:" @@ -4605,7 +4605,7 @@ msgstr "很抱æ‰ï¼Œæˆ‘ä»¬æ— æ³•è§£æžæ¤åˆ—表。如果问题æŒç»å‘生,请 #: src/components/dialogs/MutedWords.tsx:230 msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." -msgstr "" +msgstr "很抱æ‰ï¼Œæˆ‘ä»¬æ— æ³•åŠ è½½ä½ çš„éšè—è¯åˆ—表。请é‡è¯•。" #: src/view/screens/Search/Search.tsx:254 msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." @@ -4713,7 +4713,7 @@ msgstr "ä½ ç›®å‰è¿˜æ²¡æœ‰ä»»ä½•ä¿å˜çš„ä¿¡æ¯æµã€‚" #: src/view/com/post-thread/PostThread.tsx:465 msgid "You have blocked the author or you have been blocked by the author." -msgstr "ä½ å·²å±è”½è¯¥ä½œè€…ï¼Œæˆ–ä½ å·²è¢«è¯¥ä½œè€…å±è”½ã€‚" +msgstr "ä½ å·²å±è”½è¯¥å¸–åä½œè€…ï¼Œæˆ–ä½ å·²è¢«è¯¥ä½œè€…å±è”½ã€‚" #: src/view/com/modals/ModerationDetails.tsx:56 msgid "You have blocked this user. You cannot view their content." @@ -4724,7 +4724,7 @@ msgstr "ä½ å·²å±è”½äº†æ¤ç”¨æˆ·ï¼Œä½ å°†æ— æ³•æŸ¥çœ‹ä»–ä»¬å‘布的内容。" #: src/view/com/modals/ChangePassword.tsx:87 #: src/view/com/modals/ChangePassword.tsx:121 msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." -msgstr "ä½ è¾“å…¥çš„é‚€è¯·ç æ— 效。它应该长得åƒè¿™æ · XXXXX-XXXXX。" +msgstr "ä½ è¾“å…¥çš„ç¡®è®¤ç æ— 效。它应该长得åƒè¿™æ · XXXXX-XXXXX。" #: src/view/com/modals/ModerationDetails.tsx:87 msgid "You have muted this user." @@ -4745,7 +4745,7 @@ msgstr "ä½ è¿˜æ²¡æœ‰å±è”½ä»»ä½•è´¦å·ã€‚è¦å±è”½è´¦å·ï¼Œè¯·è½¬åˆ°å…¶ä¸ªäººèµ„ #: src/view/screens/AppPasswords.tsx:87 msgid "You have not created any app passwords yet. You can create one by pressing the button below." -msgstr "ä½ å°šæœªåˆ›å»ºä»»ä½• App 专用密ç ,å¯ä»¥é€šè¿‡ç‚¹å‡»ä¸‹é¢çš„æŒ‰é’®æ¥åˆ›å»ºä¸€ä¸ªã€‚" +msgstr "ä½ å°šæœªåˆ›å»ºä»»ä½•åº”ç”¨ä¸“ç”¨å¯†ç ,å¯ä»¥é€šè¿‡ç‚¹å‡»ä¸‹é¢çš„æŒ‰é’®æ¥åˆ›å»ºä¸€ä¸ªã€‚" #: src/view/screens/ModerationMutedAccounts.tsx:131 msgid "You have not muted any accounts yet. To mute an account, go to their profile and selected \"Mute account\" from the menu on their account." @@ -4753,7 +4753,7 @@ msgstr "ä½ è¿˜æ²¡æœ‰éšè—任何账å·ã€‚è¦éšè—è´¦å·ï¼Œè¯·è½¬åˆ°å…¶ä¸ªäººèµ„ #: src/components/dialogs/MutedWords.tsx:250 msgid "You haven't muted any words or tags yet" -msgstr "" +msgstr "ä½ è¿˜æ²¡æœ‰éšè—ä»»ä½•è¯æˆ–è¯é¢˜æ ‡ç¾" #: src/view/com/modals/ContentFilteringSettings.tsx:175 msgid "You must be 18 or older to enable adult content." @@ -4773,7 +4773,7 @@ msgstr "ä½ å°†æ”¶åˆ°è¿™æ¡è®¨è®ºä¸²çš„通知" #: src/view/com/auth/login/SetNewPasswordForm.tsx:107 msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password." -msgstr "ä½ å°†æ”¶åˆ°ä¸€å°å¸¦æœ‰é‡ç½®ä»£ç 的电å邮件。请在æ¤è¾“入该é‡ç½®ä»£ç ,然åŽè¾“å…¥ä½ çš„æ–°å¯†ç 。" +msgstr "ä½ å°†æ”¶åˆ°ä¸€å°å¸¦æœ‰ç¡®è®¤ç 的电å邮件。请在æ¤è¾“入该确认ç ,然åŽè¾“å…¥ä½ çš„æ–°å¯†ç 。" #: src/screens/Onboarding/StepModeration/index.tsx:72 msgid "You're in control" @@ -4803,7 +4803,7 @@ msgstr "ä½ çš„è´¦æˆ·å·²åˆ é™¤" #: src/view/screens/Settings/ExportCarDialog.tsx:47 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 "" +msgstr "æ‚¨çš„å¸æˆ·æ•°æ®åº“åŒ…å«æ‰€æœ‰å…¬å…±æ•°æ®è®°å½•,它们将被导出为“CARâ€æ–‡ä»¶ã€‚æ¤æ–‡ä»¶ä¸åŒ…括帖åä¸çš„åª’ä½“ï¼Œä¾‹å¦‚å›¾åƒæˆ–您的éšç§æ•°æ®ï¼Œè¿™äº›æ•°æ®éœ€è¦å¦å¤–获å–。" #: src/view/com/auth/create/Step1.tsx:215 msgid "Your birth date" @@ -4837,7 +4837,7 @@ msgstr "ä½ çš„ç”µå邮箱尚未验è¯ã€‚这是一个é‡è¦çš„安全æ¥éª¤ï¼Œæˆ‘ #: src/view/com/posts/FollowingEmptyState.tsx:47 msgid "Your following feed is empty! Follow more users to see what's happening." -msgstr "ä½ çš„å…³æ³¨ä¿¡æ¯æµä¸ºç©ºï¼å…³æ³¨æ›´å¤šç”¨æˆ·åŽ»çœ‹çœ‹ä»–ä»¬å‘了什么什么。" +msgstr "ä½ çš„å…³æ³¨ä¿¡æ¯æµä¸ºç©ºï¼å…³æ³¨æ›´å¤šç”¨æˆ·åŽ»çœ‹çœ‹ä»–ä»¬å‘了什么。" #: src/view/com/auth/create/Step2.tsx:83 msgid "Your full handle will be" @@ -4851,25 +4851,25 @@ msgstr "ä½ çš„å®Œæ•´ç”¨æˆ·è¯†åˆ«ç¬¦å°†ä¿®æ”¹ä¸º <0>@{0}</0>" #: src/view/shell/desktop/RightNav.tsx:137 #: src/view/shell/Drawer.tsx:660 #~ msgid "Your invite codes are hidden when logged in using an App Password" -#~ msgstr "在使用 App 专用密ç ç™»å½•æ—¶ï¼Œä½ çš„é‚€è¯·ç 将被éšè—" +#~ msgstr "在使用应用专用密ç ç™»å½•æ—¶ï¼Œä½ çš„é‚€è¯·ç 将被éšè—" #: src/components/dialogs/MutedWords.tsx:221 msgid "Your muted words" -msgstr "" +msgstr "ä½ çš„éšè—è¯" #: src/view/com/modals/ChangePassword.tsx:155 msgid "Your password has been changed successfully!" -msgstr "ä½ çš„å¯†ç 已更改æˆåŠŸï¼" +msgstr "ä½ çš„å¯†ç å·²æˆåŠŸæ›´æ”¹ï¼" #: src/view/com/composer/Composer.tsx:274 msgid "Your post has been published" -msgstr "ä½ çš„å¸–åå·²å‘é€" +msgstr "ä½ çš„å¸–åå·²å‘布" #: src/screens/Onboarding/StepFinished.tsx:105 #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:59 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:59 msgid "Your posts, likes, and blocks are public. Mutes are private." -msgstr "ä½ çš„å¸–åã€ç‚¹èµžå’Œå±è”½æ˜¯å…¬å¼€å¯è§çš„,而éšè—ä¸å¯è§ã€‚" +msgstr "ä½ çš„å¸–åã€å–œæ¬¢å’Œå±è”½æ˜¯å…¬å¼€å¯è§çš„,而éšè—ä¸å¯è§ã€‚" #: src/view/com/modals/SwitchAccount.tsx:84 #: src/view/screens/Settings/index.tsx:118 @@ -4878,7 +4878,7 @@ msgstr "ä½ çš„ä¸ªäººèµ„æ–™" #: src/view/com/composer/Composer.tsx:273 msgid "Your reply has been published" -msgstr "ä½ çš„å›žå¤å·²å‘é€" +msgstr "ä½ çš„å›žå¤å·²å‘布" #: src/view/com/auth/create/Step2.tsx:65 msgid "Your user handle" diff --git a/src/routes.ts b/src/routes.ts index 5c263fd6f..f6f372947 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -21,7 +21,9 @@ export const router = new Router({ PostRepostedBy: '/profile/:name/post/:rkey/reposted-by', ProfileFeed: '/profile/:name/feed/:rkey', ProfileFeedLikedBy: '/profile/:name/feed/:rkey/liked-by', + ProfileLabelerLikedBy: '/profile/:name/labeler/liked-by', Debug: '/sys/debug', + DebugMod: '/sys/debug-mod', Log: '/sys/log', AppPasswords: '/settings/app-passwords', PreferencesFollowingFeed: '/settings/following-feed', diff --git a/src/screens/Hashtag.tsx b/src/screens/Hashtag.tsx index 776cc585e..46452f087 100644 --- a/src/screens/Hashtag.tsx +++ b/src/screens/Hashtag.tsx @@ -128,8 +128,8 @@ export default function HashtagScreen({ isError={isError} isEmpty={posts.length < 1} onRetry={refetch} - notFoundType="results" - empty={_(msg`We couldn't find any results for that hashtag.`)} + emptyTitle="results" + emptyMessage={_(msg`We couldn't find any results for that hashtag.`)} /> {!isLoading && posts.length > 0 && ( <List<PostView> diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx new file mode 100644 index 000000000..d73823fad --- /dev/null +++ b/src/screens/Moderation/index.tsx @@ -0,0 +1,556 @@ +import React from 'react' +import {View} from 'react-native' +import {useFocusEffect} from '@react-navigation/native' +import {ComAtprotoLabelDefs} from '@atproto/api' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {LABELS} from '@atproto/api' +import {useSafeAreaFrame} from 'react-native-safe-area-context' + +import {NativeStackScreenProps, CommonNavigatorParams} from '#/lib/routes/types' +import {CenteredView} from '#/view/com/util/Views' +import {ViewHeader} from '#/view/com/util/ViewHeader' +import {useAnalytics} from 'lib/analytics/analytics' +import {useSetMinimalShellMode} from '#/state/shell' +import {useSession} from '#/state/session' +import { + useProfileQuery, + useProfileUpdateMutation, +} from '#/state/queries/profile' +import {ScrollView} from '#/view/com/util/Views' + +import { + UsePreferencesQueryResponse, + useMyLabelersQuery, + usePreferencesQuery, + usePreferencesSetAdultContentMutation, +} from '#/state/queries/preferences' + +import {getLabelingServiceTitle} from '#/lib/moderation' +import {logger} from '#/logger' +import {useTheme, atoms as a, useBreakpoints, ViewStyleProp} from '#/alf' +import {Divider} from '#/components/Divider' +import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign' +import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group' +import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person' +import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' +import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter' +import {Text} from '#/components/Typography' +import * as Toggle from '#/components/forms/Toggle' +import {InlineLink, Link} from '#/components/Link' +import {Button, ButtonText} from '#/components/Button' +import {Loader} from '#/components/Loader' +import * as LabelingService from '#/components/LabelingServiceCard' +import {GlobalModerationLabelPref} from '#/components/moderation/GlobalModerationLabelPref' +import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' +import {Props as SVGIconProps} from '#/components/icons/common' +import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' +import * as Dialog from '#/components/Dialog' + +function ErrorState({error}: {error: string}) { + const t = useTheme() + return ( + <View style={[a.p_xl]}> + <Text + style={[ + a.text_md, + a.leading_normal, + a.pb_md, + t.atoms.text_contrast_medium, + ]}> + <Trans> + Hmmmm, it seems we're having trouble loading this data. See below for + more details. If this issue persists, please contact us. + </Trans> + </Text> + <View + style={[ + a.relative, + a.py_md, + a.px_lg, + a.rounded_md, + a.mb_2xl, + t.atoms.bg_contrast_25, + ]}> + <Text style={[a.text_md, a.leading_normal]}>{error}</Text> + </View> + </View> + ) +} + +export function ModerationScreen( + _props: NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>, +) { + const t = useTheme() + const {_} = useLingui() + const { + isLoading: isPreferencesLoading, + error: preferencesError, + data: preferences, + } = usePreferencesQuery() + const {gtMobile} = useBreakpoints() + const {height} = useSafeAreaFrame() + + const isLoading = isPreferencesLoading + const error = preferencesError + + return ( + <CenteredView + testID="moderationScreen" + style={[ + t.atoms.border_contrast_low, + t.atoms.bg, + {minHeight: height}, + ...(gtMobile ? [a.border_l, a.border_r] : []), + ]}> + <ViewHeader title={_(msg`Moderation`)} showOnDesktop /> + + {isLoading ? ( + <View style={[a.w_full, a.align_center, a.pt_2xl]}> + <Loader size="xl" fill={t.atoms.text.color} /> + </View> + ) : error || !preferences ? ( + <ErrorState + error={ + preferencesError?.toString() || + _(msg`Something went wrong, please try again.`) + } + /> + ) : ( + <ModerationScreenInner preferences={preferences} /> + )} + </CenteredView> + ) +} + +function SubItem({ + title, + icon: Icon, + style, +}: ViewStyleProp & { + title: string + icon: React.ComponentType<SVGIconProps> +}) { + const t = useTheme() + return ( + <View + style={[ + a.w_full, + a.flex_row, + a.align_center, + a.justify_between, + a.p_lg, + a.gap_sm, + style, + ]}> + <View style={[a.flex_row, a.align_center, a.gap_md]}> + <Icon size="md" style={[t.atoms.text_contrast_medium]} /> + <Text style={[a.text_sm, a.font_bold]}>{title}</Text> + </View> + <ChevronRight + size="sm" + style={[t.atoms.text_contrast_low, a.self_end, {paddingBottom: 2}]} + /> + </View> + ) +} + +export function ModerationScreenInner({ + preferences, +}: { + preferences: UsePreferencesQueryResponse +}) { + const {_} = useLingui() + const t = useTheme() + const setMinimalShellMode = useSetMinimalShellMode() + const {screen} = useAnalytics() + const {gtMobile} = useBreakpoints() + const {mutedWordsDialogControl} = useGlobalDialogsControlContext() + const birthdateDialogControl = Dialog.useDialogControl() + const { + isLoading: isLabelersLoading, + data: labelers, + error: labelersError, + } = useMyLabelersQuery() + + useFocusEffect( + React.useCallback(() => { + screen('Moderation') + setMinimalShellMode(false) + }, [screen, setMinimalShellMode]), + ) + + const {mutateAsync: setAdultContentPref, variables: optimisticAdultContent} = + usePreferencesSetAdultContentMutation() + const adultContentEnabled = !!( + (optimisticAdultContent && optimisticAdultContent.enabled) || + (!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled) + ) + const ageNotSet = !preferences.userAge + const isUnderage = (preferences.userAge || 0) < 18 + + const onToggleAdultContentEnabled = React.useCallback( + async (selected: boolean) => { + try { + await setAdultContentPref({ + enabled: selected, + }) + } catch (e: any) { + logger.error(`Failed to set adult content pref`, { + message: e.message, + }) + } + }, + [setAdultContentPref], + ) + + return ( + <ScrollView + contentContainerStyle={[ + a.border_0, + a.pt_2xl, + a.px_lg, + gtMobile && a.px_2xl, + ]}> + <Text + style={[a.text_md, a.font_bold, a.pb_md, t.atoms.text_contrast_high]}> + <Trans>Moderation tools</Trans> + </Text> + + <View + style={[ + a.w_full, + a.rounded_md, + a.overflow_hidden, + t.atoms.bg_contrast_25, + ]}> + <Button + testID="mutedWordsBtn" + label={_(msg`Open muted words and tags settings`)} + onPress={() => mutedWordsDialogControl.open()}> + {state => ( + <SubItem + title={_(msg`Muted words & tags`)} + icon={Filter} + style={[ + (state.hovered || state.pressed) && [t.atoms.bg_contrast_50], + ]} + /> + )} + </Button> + <Divider /> + <Link testID="moderationlistsBtn" to="/moderation/modlists"> + {state => ( + <SubItem + title={_(msg`Moderation lists`)} + icon={Group} + style={[ + (state.hovered || state.pressed) && [t.atoms.bg_contrast_50], + ]} + /> + )} + </Link> + <Divider /> + <Link testID="mutedAccountsBtn" to="/moderation/muted-accounts"> + {state => ( + <SubItem + title={_(msg`Muted accounts`)} + icon={Person} + style={[ + (state.hovered || state.pressed) && [t.atoms.bg_contrast_50], + ]} + /> + )} + </Link> + <Divider /> + <Link testID="blockedAccountsBtn" to="/moderation/blocked-accounts"> + {state => ( + <SubItem + title={_(msg`Blocked accounts`)} + icon={CircleBanSign} + style={[ + (state.hovered || state.pressed) && [t.atoms.bg_contrast_50], + ]} + /> + )} + </Link> + </View> + + <Text + style={[ + a.pt_2xl, + a.pb_md, + a.text_md, + a.font_bold, + t.atoms.text_contrast_high, + ]}> + <Trans>Content filters</Trans> + </Text> + + <View style={[a.gap_md]}> + {ageNotSet && ( + <> + <Button + label={_(msg`Confirm your birthdate`)} + size="small" + variant="solid" + color="secondary" + onPress={() => { + birthdateDialogControl.open() + }} + style={[a.justify_between, a.rounded_md, a.px_lg, a.py_lg]}> + <ButtonText> + <Trans>Confirm your age:</Trans> + </ButtonText> + <ButtonText> + <Trans>Set birthdate</Trans> + </ButtonText> + </Button> + + <BirthDateSettingsDialog control={birthdateDialogControl} /> + </> + )} + <View + style={[ + a.w_full, + a.rounded_md, + a.overflow_hidden, + t.atoms.bg_contrast_25, + ]}> + {!ageNotSet && !isUnderage && ( + <> + <View + style={[ + a.py_lg, + a.px_lg, + a.flex_row, + a.align_center, + a.justify_between, + ]}> + <Text style={[a.font_semibold, t.atoms.text_contrast_high]}> + <Trans>Enable adult content</Trans> + </Text> + <Toggle.Item + label={_(msg`Toggle to enable or disable adult content`)} + name="adultContent" + value={adultContentEnabled} + onChange={onToggleAdultContentEnabled}> + <View style={[a.flex_row, a.align_center, a.gap_sm]}> + <Text style={[t.atoms.text_contrast_medium]}> + {adultContentEnabled ? ( + <Trans>Enabled</Trans> + ) : ( + <Trans>Disabled</Trans> + )} + </Text> + <Toggle.Switch /> + </View> + </Toggle.Item> + </View> + <Divider /> + </> + )} + {!isUnderage && adultContentEnabled && ( + <> + <GlobalModerationLabelPref labelValueDefinition={LABELS.porn} /> + <Divider /> + <GlobalModerationLabelPref labelValueDefinition={LABELS.sexual} /> + <Divider /> + <GlobalModerationLabelPref + labelValueDefinition={LABELS['graphic-media']} + /> + <Divider /> + </> + )} + <GlobalModerationLabelPref labelValueDefinition={LABELS.nudity} /> + </View> + </View> + + <Text + style={[ + a.text_md, + a.font_bold, + a.pt_2xl, + a.pb_md, + t.atoms.text_contrast_high, + ]}> + <Trans>Advanced</Trans> + </Text> + + {isLabelersLoading ? ( + <View style={[a.w_full, a.align_center, a.p_lg]}> + <Loader size="xl" /> + </View> + ) : labelersError || !labelers ? ( + <View style={[a.p_lg, a.rounded_sm, t.atoms.bg_contrast_25]}> + <Text> + <Trans> + We were unable to load your configured labelers at this time. + </Trans> + </Text> + </View> + ) : ( + <View style={[a.rounded_sm, t.atoms.bg_contrast_25]}> + {labelers.map((labeler, i) => { + return ( + <React.Fragment key={labeler.creator.did}> + {i !== 0 && <Divider />} + <LabelingService.Link labeler={labeler}> + {state => ( + <LabelingService.Outer + style={[ + i === 0 && { + borderTopLeftRadius: a.rounded_sm.borderRadius, + borderTopRightRadius: a.rounded_sm.borderRadius, + }, + i === labelers.length - 1 && { + borderBottomLeftRadius: a.rounded_sm.borderRadius, + borderBottomRightRadius: a.rounded_sm.borderRadius, + }, + (state.hovered || state.pressed) && [ + t.atoms.bg_contrast_50, + ], + ]}> + <LabelingService.Avatar avatar={labeler.creator.avatar} /> + <LabelingService.Content> + <LabelingService.Title + value={getLabelingServiceTitle({ + displayName: labeler.creator.displayName, + handle: labeler.creator.handle, + })} + /> + <LabelingService.Description + value={labeler.creator.description} + handle={labeler.creator.handle} + /> + </LabelingService.Content> + </LabelingService.Outer> + )} + </LabelingService.Link> + </React.Fragment> + ) + })} + </View> + )} + + <Text + style={[ + a.text_md, + a.font_bold, + a.pt_2xl, + a.pb_md, + t.atoms.text_contrast_high, + ]}> + <Trans>Logged-out visibility</Trans> + </Text> + + <PwiOptOut /> + + <View style={{height: 200}} /> + </ScrollView> + ) +} + +function PwiOptOut() { + const t = useTheme() + const {_} = useLingui() + const {currentAccount} = useSession() + const {data: profile} = useProfileQuery({did: currentAccount?.did}) + const updateProfile = useProfileUpdateMutation() + + const isOptedOut = + profile?.labels?.some(l => l.val === '!no-unauthenticated') || false + const canToggle = profile && !updateProfile.isPending + + const onToggleOptOut = React.useCallback(() => { + if (!profile) { + return + } + let wasAdded = false + updateProfile.mutate({ + profile, + updates: existing => { + // create labels attr if needed + existing.labels = ComAtprotoLabelDefs.isSelfLabels(existing.labels) + ? existing.labels + : { + $type: 'com.atproto.label.defs#selfLabels', + values: [], + } + + // toggle the label + const hasLabel = existing.labels.values.some( + l => l.val === '!no-unauthenticated', + ) + if (hasLabel) { + wasAdded = false + existing.labels.values = existing.labels.values.filter( + l => l.val !== '!no-unauthenticated', + ) + } else { + wasAdded = true + existing.labels.values.push({val: '!no-unauthenticated'}) + } + + // delete if no longer needed + if (existing.labels.values.length === 0) { + delete existing.labels + } + return existing + }, + checkCommitted: res => { + const exists = !!res.data.labels?.some( + l => l.val === '!no-unauthenticated', + ) + return exists === wasAdded + }, + }) + }, [updateProfile, profile]) + + return ( + <View style={[a.pt_sm]}> + <View style={[a.flex_row, a.align_center, a.justify_between, a.gap_lg]}> + <Toggle.Item + disabled={!canToggle} + value={isOptedOut} + onChange={onToggleOptOut} + name="logged_out_visibility" + style={a.flex_1} + label={_( + msg`Discourage apps from showing my account to logged-out users`, + )}> + <Toggle.Switch /> + <Toggle.Label style={[a.text_md, a.flex_1]}> + <Trans> + Discourage apps from showing my account to logged-out users + </Trans> + </Toggle.Label> + </Toggle.Item> + + {updateProfile.isPending && <Loader />} + </View> + + <View style={[a.pt_md, a.gap_md, {paddingLeft: 38}]}> + <Text style={[a.leading_snug, t.atoms.text_contrast_high]}> + <Trans> + 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. + </Trans> + </Text> + <Text style={[a.font_bold, a.leading_snug, t.atoms.text_contrast_high]}> + <Trans> + 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. + </Trans> + </Text> + + <InlineLink to="https://blueskyweb.zendesk.com/hc/en-us/articles/15835264007693-Data-Privacy"> + <Trans>Learn more about what is public on Bluesky.</Trans> + </InlineLink> + </View> + </View> + ) +} diff --git a/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx b/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx index 360025c02..aaacaea0a 100644 --- a/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx +++ b/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx @@ -56,7 +56,9 @@ export function AdultContentEnabledPref({ try { mutate({ - enabled: !(variables?.enabled ?? preferences?.adultContentEnabled), + enabled: !( + variables?.enabled ?? preferences?.moderationPrefs.adultContentEnabled + ), }) } catch (e) { Toast.show( @@ -75,7 +77,10 @@ export function AdultContentEnabledPref({ <Toggle.Item name={_(msg`Enable adult content in your feeds`)} label={_(msg`Enable adult content in your feeds`)} - value={variables?.enabled ?? preferences?.adultContentEnabled} + value={ + variables?.enabled ?? + preferences?.moderationPrefs.adultContentEnabled + } onChange={onToggleAdultContent}> <View style={[ @@ -85,7 +90,9 @@ export function AdultContentEnabledPref({ a.align_center, a.py_md, ]}> - <Text style={[a.font_bold]}>Enable Adult Content</Text> + <Text style={[a.font_bold]}> + <Trans>Enable Adult Content</Trans> + </Text> <Toggle.Switch /> </View> </Toggle.Item> @@ -106,7 +113,9 @@ export function AdultContentEnabledPref({ )} <Prompt.Outer control={prompt}> - <Prompt.Title>Adult Content</Prompt.Title> + <Prompt.Title> + <Trans>Adult Content</Trans> + </Prompt.Title> <Prompt.Description> <Trans> Due to Apple policies, adult content can only be enabled on the web @@ -114,7 +123,9 @@ export function AdultContentEnabledPref({ </Trans> </Prompt.Description> <Prompt.Actions> - <Prompt.Action onPress={() => prompt.close()}>OK</Prompt.Action> + <Prompt.Action onPress={() => prompt.close()}> + <Trans>OK</Trans> + </Prompt.Action> </Prompt.Actions> </Prompt.Outer> </> diff --git a/src/screens/Onboarding/StepModeration/ModerationOption.tsx b/src/screens/Onboarding/StepModeration/ModerationOption.tsx index c61b520ba..ac02a874c 100644 --- a/src/screens/Onboarding/StepModeration/ModerationOption.tsx +++ b/src/screens/Onboarding/StepModeration/ModerationOption.tsx @@ -1,40 +1,51 @@ import React from 'react' import {View} from 'react-native' -import {LabelPreference} from '@atproto/api' +import {LabelPreference, InterpretedLabelValueDefinition} from '@atproto/api' import {useLingui} from '@lingui/react' -import {msg} from '@lingui/macro' -import Animated, {Easing, Layout, FadeIn} from 'react-native-reanimated' +import {msg, Trans} from '@lingui/macro' import { - CONFIGURABLE_LABEL_GROUPS, - ConfigurableLabelGroup, usePreferencesQuery, usePreferencesSetContentLabelMutation, } from '#/state/queries/preferences' import {atoms as a, useTheme} from '#/alf' import {Text} from '#/components/Typography' import * as ToggleButton from '#/components/forms/ToggleButton' +import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' export function ModerationOption({ - labelGroup, - isMounted, + labelValueDefinition, + disabled, }: { - labelGroup: ConfigurableLabelGroup - isMounted: React.MutableRefObject<boolean> + labelValueDefinition: InterpretedLabelValueDefinition + disabled?: boolean }) { const {_} = useLingui() const t = useTheme() - const groupInfo = CONFIGURABLE_LABEL_GROUPS[labelGroup] const {data: preferences} = usePreferencesQuery() const {mutate, variables} = usePreferencesSetContentLabelMutation() + const label = labelValueDefinition.identifier const visibility = - variables?.visibility ?? preferences?.contentLabels?.[labelGroup] + variables?.visibility ?? preferences?.moderationPrefs.labels?.[label] + + const allLabelStrings = useGlobalLabelStrings() + const labelStrings = + labelValueDefinition.identifier in allLabelStrings + ? allLabelStrings[labelValueDefinition.identifier] + : { + name: labelValueDefinition.identifier, + description: `Labeled "${labelValueDefinition.identifier}"`, + } const onChange = React.useCallback( (vis: string[]) => { - mutate({labelGroup, visibility: vis[0] as LabelPreference}) + mutate({ + label, + visibility: vis[0] as LabelPreference, + labelerDid: undefined, + }) }, - [mutate, labelGroup], + [mutate, label], ) const labels = { @@ -44,7 +55,7 @@ export function ModerationOption({ } return ( - <Animated.View + <View style={[ a.flex_row, a.justify_between, @@ -52,33 +63,37 @@ export function ModerationOption({ a.py_xs, a.px_xs, a.align_center, - ]} - layout={Layout.easing(Easing.ease).duration(200)} - entering={isMounted.current ? FadeIn : undefined}> - <View style={[a.gap_xs, {width: '50%'}]}> - <Text style={[a.font_bold]}>{groupInfo.title}</Text> + ]}> + <View style={[a.gap_xs, a.flex_1]}> + <Text style={[a.font_bold]}>{labelStrings.name}</Text> <Text style={[t.atoms.text_contrast_medium, a.leading_snug]}> - {groupInfo.subtitle} + {labelStrings.description} </Text> </View> - <View style={[a.justify_center, {minHeight: 35}]}> - <ToggleButton.Group - label={_( - msg`Configure content filtering setting for category: ${groupInfo.title.toLowerCase()}`, - )} - values={[visibility ?? 'hide']} - onChange={onChange}> - <ToggleButton.Button name="hide" label={labels.hide}> - {labels.hide} - </ToggleButton.Button> - <ToggleButton.Button name="warn" label={labels.warn}> - {labels.warn} - </ToggleButton.Button> - <ToggleButton.Button name="ignore" label={labels.show}> - {labels.show} - </ToggleButton.Button> - </ToggleButton.Group> + <View style={[a.justify_center, {minHeight: 40}]}> + {disabled ? ( + <Text style={[a.font_bold]}> + <Trans>Hide</Trans> + </Text> + ) : ( + <ToggleButton.Group + label={_( + msg`Configure content filtering setting for category: ${labelStrings.name.toLowerCase()}`, + )} + values={[visibility ?? 'hide']} + onChange={onChange}> + <ToggleButton.Button name="ignore" label={labels.show}> + {labels.show} + </ToggleButton.Button> + <ToggleButton.Button name="warn" label={labels.warn}> + {labels.warn} + </ToggleButton.Button> + <ToggleButton.Button name="hide" label={labels.hide}> + {labels.hide} + </ToggleButton.Button> + </ToggleButton.Group> + )} </View> - </Animated.View> + </View> ) } diff --git a/src/screens/Onboarding/StepModeration/index.tsx b/src/screens/Onboarding/StepModeration/index.tsx index 543a5b159..9b52f9f43 100644 --- a/src/screens/Onboarding/StepModeration/index.tsx +++ b/src/screens/Onboarding/StepModeration/index.tsx @@ -2,15 +2,10 @@ import React from 'react' import {View} from 'react-native' import {useLingui} from '@lingui/react' import {msg, Trans} from '@lingui/macro' -import Animated, {Easing, Layout} from 'react-native-reanimated' +import {LABELS} from '@atproto/api' import {atoms as a} from '#/alf' -import { - configurableAdultLabelGroups, - configurableOtherLabelGroups, - usePreferencesSetAdultContentMutation, -} from 'state/queries/preferences' -import {Divider} from '#/components/Divider' +import {usePreferencesSetAdultContentMutation} from 'state/queries/preferences' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' @@ -28,14 +23,6 @@ import {AdultContentEnabledPref} from '#/screens/Onboarding/StepModeration/Adult import {Context} from '#/screens/Onboarding/state' import {IconCircle} from '#/components/IconCircle' -function AnimatedDivider() { - return ( - <Animated.View layout={Layout.easing(Easing.ease).duration(200)}> - <Divider /> - </Animated.View> - ) -} - export function StepModeration() { const {_} = useLingui() const {track} = useAnalytics() @@ -52,7 +39,7 @@ export function StepModeration() { const adultContentEnabled = !!( (variables && variables.enabled) || - (!variables && preferences?.adultContentEnabled) + (!variables && preferences?.moderationPrefs.adultContentEnabled) ) const onContinue = React.useCallback(() => { @@ -86,22 +73,19 @@ export function StepModeration() { <AdultContentEnabledPref mutate={mutate} variables={variables} /> <View style={[a.gap_sm, a.w_full]}> - {adultContentEnabled && - configurableAdultLabelGroups.map((g, index) => ( - <React.Fragment key={index}> - {index === 0 && <AnimatedDivider />} - <ModerationOption labelGroup={g} isMounted={isMounted} /> - <AnimatedDivider /> - </React.Fragment> - ))} - - {configurableOtherLabelGroups.map((g, index) => ( - <React.Fragment key={index}> - {!adultContentEnabled && index === 0 && <AnimatedDivider />} - <ModerationOption labelGroup={g} isMounted={isMounted} /> - <AnimatedDivider /> - </React.Fragment> - ))} + <ModerationOption + labelValueDefinition={LABELS.porn} + disabled={!adultContentEnabled} + /> + <ModerationOption + labelValueDefinition={LABELS.sexual} + disabled={!adultContentEnabled} + /> + <ModerationOption + labelValueDefinition={LABELS['graphic-media']} + disabled={!adultContentEnabled} + /> + <ModerationOption labelValueDefinition={LABELS.nudity} /> </View> </> )} diff --git a/src/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard.tsx b/src/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard.tsx index 067005892..7e4ea1f8b 100644 --- a/src/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard.tsx +++ b/src/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard.tsx @@ -88,7 +88,7 @@ export function SuggestedAccountCard({ <UserAvatar size={48} avatar={profile.avatar} - moderation={moderation.avatar} + moderation={moderation.ui('avatar')} /> </View> <View style={[a.flex_1]}> diff --git a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx index 14faddc10..bdf94d824 100644 --- a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx +++ b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx @@ -76,7 +76,7 @@ export function StepSuggestedAccounts() { return aggregateInterestItems( state.interestsStepResults.selectedInterests, state.interestsStepResults.apiResponse.suggestedAccountDids, - state.interestsStepResults.apiResponse.suggestedAccountDids.default, + state.interestsStepResults.apiResponse.suggestedAccountDids.default || [], ) }, [state.interestsStepResults]) const moderationOpts = useModerationOpts() diff --git a/src/screens/Onboarding/StepTopicalFeeds.tsx b/src/screens/Onboarding/StepTopicalFeeds.tsx index 636565e34..089363c23 100644 --- a/src/screens/Onboarding/StepTopicalFeeds.tsx +++ b/src/screens/Onboarding/StepTopicalFeeds.tsx @@ -21,7 +21,7 @@ import { import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard' import {aggregateInterestItems} from '#/screens/Onboarding/util' import {IconCircle} from '#/components/IconCircle' -import {IS_PROD_SERVICE} from 'lib/constants' +import {IS_TEST_USER} from 'lib/constants' import {useSession} from 'state/session' export function StepTopicalFeeds() { @@ -32,14 +32,14 @@ export function StepTopicalFeeds() { const [selectedFeedUris, setSelectedFeedUris] = React.useState<string[]>([]) const [saving, setSaving] = React.useState(false) const suggestedFeedUris = React.useMemo(() => { - if (!IS_PROD_SERVICE(currentAccount?.service)) return [] + if (IS_TEST_USER(currentAccount?.handle)) return [] return aggregateInterestItems( state.interestsStepResults.selectedInterests, state.interestsStepResults.apiResponse.suggestedFeedUris, - state.interestsStepResults.apiResponse.suggestedFeedUris.default, + state.interestsStepResults.apiResponse.suggestedFeedUris.default || [], ).slice(0, 10) }, [ - currentAccount?.service, + currentAccount?.handle, state.interestsStepResults.apiResponse.suggestedFeedUris, state.interestsStepResults.selectedInterests, ]) diff --git a/src/screens/Profile/ErrorState.tsx b/src/screens/Profile/ErrorState.tsx new file mode 100644 index 000000000..2ec2cf592 --- /dev/null +++ b/src/screens/Profile/ErrorState.tsx @@ -0,0 +1,72 @@ +import React from 'react' +import {View} from 'react-native' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {useTheme, atoms as a} from '#/alf' +import {Text} from '#/components/Typography' +import {Button, ButtonText} from '#/components/Button' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import {NavigationProp} from '#/lib/routes/types' + +export function ErrorState({error}: {error: string}) { + const t = useTheme() + const {_} = useLingui() + const navigation = useNavigation<NavigationProp>() + + const onPressBack = React.useCallback(() => { + if (navigation.canGoBack()) { + navigation.goBack() + } else { + navigation.navigate('Home') + } + }, [navigation]) + + return ( + <View style={[a.px_xl]}> + <CircleInfo width={48} style={[t.atoms.text_contrast_low]} /> + + <Text style={[a.text_xl, a.font_bold, a.pb_md, a.pt_xl]}> + <Trans>Hmmmm, we couldn't load that moderation service.</Trans> + </Text> + <Text + style={[ + a.text_md, + a.leading_normal, + a.pb_md, + t.atoms.text_contrast_medium, + ]}> + <Trans> + This moderation service is unavailable. See below for more details. If + this issue persists, contact us. + </Trans> + </Text> + <View + style={[ + a.relative, + a.py_md, + a.px_lg, + a.rounded_md, + a.mb_2xl, + t.atoms.bg_contrast_25, + ]}> + <Text style={[a.text_md, a.leading_normal]}>{error}</Text> + </View> + + <View style={{flexDirection: 'row'}}> + <Button + size="small" + color="secondary" + variant="solid" + label={_(msg`Go Back`)} + accessibilityHint="Return to previous page" + onPress={onPressBack}> + <ButtonText> + <Trans>Go Back</Trans> + </ButtonText> + </Button> + </View> + </View> + ) +} diff --git a/src/screens/Profile/Header/DisplayName.tsx b/src/screens/Profile/Header/DisplayName.tsx new file mode 100644 index 000000000..b6d88db71 --- /dev/null +++ b/src/screens/Profile/Header/DisplayName.tsx @@ -0,0 +1,31 @@ +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 {atoms as a, useTheme} from '#/alf' +import {Text} from '#/components/Typography' + +export function ProfileHeaderDisplayName({ + profile, + moderation, +}: { + profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> + moderation: ModerationDecision +}) { + const t = useTheme() + return ( + <View pointerEvents="none"> + <Text + testID="profileHeaderDisplayName" + style={[t.atoms.text, a.text_4xl, {fontWeight: '500'}]}> + {sanitizeDisplayName( + profile.displayName || sanitizeHandle(profile.handle), + moderation.ui('displayName'), + )} + </Text> + </View> + ) +} diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx new file mode 100644 index 000000000..fd1cbe533 --- /dev/null +++ b/src/screens/Profile/Header/Handle.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import {View} from 'react-native' +import {AppBskyActorDefs} from '@atproto/api' +import {isInvalidHandle} from 'lib/strings/handles' +import {Shadow} from '#/state/cache/types' +import {Trans} from '@lingui/macro' + +import {atoms as a, useTheme, web} from '#/alf' +import {Text} from '#/components/Typography' + +export function ProfileHeaderHandle({ + profile, +}: { + profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> +}) { + const t = useTheme() + const invalidHandle = isInvalidHandle(profile.handle) + const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy + return ( + <View style={[a.flex_row, a.gap_xs, a.align_center]} pointerEvents="none"> + {profile.viewer?.followedBy && !blockHide ? ( + <View style={[t.atoms.bg_contrast_25, a.rounded_xs, a.px_sm, a.py_xs]}> + <Text style={[t.atoms.text, a.text_sm]}> + <Trans>Follows you</Trans> + </Text> + </View> + ) : undefined} + <Text + style={[ + invalidHandle + ? [ + a.border, + a.text_xs, + a.px_sm, + a.py_xs, + a.rounded_xs, + {borderColor: t.palette.contrast_200}, + ] + : [a.text_md, t.atoms.text_contrast_medium], + web({wordBreak: 'break-all'}), + ]}> + {invalidHandle ? <Trans>âš Invalid Handle</Trans> : `@${profile.handle}`} + </Text> + </View> + ) +} diff --git a/src/screens/Profile/Header/Metrics.tsx b/src/screens/Profile/Header/Metrics.tsx new file mode 100644 index 000000000..d9a8a01a8 --- /dev/null +++ b/src/screens/Profile/Header/Metrics.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import {View} from 'react-native' +import {AppBskyActorDefs} from '@atproto/api' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {Shadow} from '#/state/cache/types' +import {pluralize} from '#/lib/strings/helpers' +import {makeProfileLink} from 'lib/routes/links' +import {formatCount} from 'view/com/util/numeric/format' + +import {atoms as a, useTheme} from '#/alf' +import {Text} from '#/components/Typography' +import {InlineLink} from '#/components/Link' + +export function ProfileHeaderMetrics({ + profile, +}: { + profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> +}) { + const t = useTheme() + const {_} = useLingui() + const following = formatCount(profile.followsCount || 0) + const followers = formatCount(profile.followersCount || 0) + const pluralizedFollowers = pluralize(profile.followersCount || 0, 'follower') + + return ( + <View + style={[a.flex_row, a.gap_sm, a.align_center, a.pb_md]} + pointerEvents="box-none"> + <InlineLink + testID="profileHeaderFollowersButton" + style={[a.flex_row, t.atoms.text]} + to={makeProfileLink(profile, 'followers')} + label={`${followers} ${pluralizedFollowers}`}> + <Text style={[a.font_bold, a.text_md]}>{followers} </Text> + <Text style={[t.atoms.text_contrast_medium, a.text_md]}> + {pluralizedFollowers} + </Text> + </InlineLink> + <InlineLink + testID="profileHeaderFollowsButton" + style={[a.flex_row, t.atoms.text]} + to={makeProfileLink(profile, 'follows')} + label={_(msg`${following} following`)}> + <Trans> + <Text style={[a.font_bold, a.text_md]}>{following} </Text> + <Text style={[t.atoms.text_contrast_medium, a.text_md]}> + following + </Text> + </Trans> + </InlineLink> + <Text style={[a.font_bold, t.atoms.text, a.text_md]}> + {formatCount(profile.postsCount || 0)}{' '} + <Text style={[t.atoms.text_contrast_medium, a.font_normal, a.text_md]}> + {pluralize(profile.postsCount || 0, 'post')} + </Text> + </Text> + </View> + ) +} diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx new file mode 100644 index 000000000..6722ed09b --- /dev/null +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -0,0 +1,329 @@ +import React, {memo, useMemo} from 'react' +import {View} from 'react-native' +import { + AppBskyActorDefs, + AppBskyLabelerDefs, + ModerationOpts, + moderateProfile, + RichText as RichTextAPI, +} from '@atproto/api' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {RichText} from '#/components/RichText' +import {useModalControls} from '#/state/modals' +import {usePreferencesQuery} from '#/state/queries/preferences' +import {useAnalytics} from 'lib/analytics/analytics' +import {useSession} from '#/state/session' +import {Shadow} from '#/state/cache/types' +import {useProfileShadow} from 'state/cache/profile-shadow' +import {useLabelerSubscriptionMutation} from '#/state/queries/labeler' +import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like' +import {logger} from '#/logger' +import {Haptics} from '#/lib/haptics' +import {pluralize} from '#/lib/strings/helpers' +import {isAppLabeler} from '#/lib/moderation' + +import {atoms as a, useTheme, tokens} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import {Text} from '#/components/Typography' +import * as Toast from '#/view/com/util/Toast' +import {ProfileHeaderShell} from './Shell' +import {ProfileMenu} from '#/view/com/profile/ProfileMenu' +import {ProfileHeaderDisplayName} from './DisplayName' +import {ProfileHeaderHandle} from './Handle' +import {ProfileHeaderMetrics} from './Metrics' +import { + Heart2_Stroke2_Corner0_Rounded as Heart, + Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled, +} from '#/components/icons/Heart2' +import {DialogOuterProps} from '#/components/Dialog' +import * as Prompt from '#/components/Prompt' +import {Link} from '#/components/Link' + +interface Props { + profile: AppBskyActorDefs.ProfileViewDetailed + labeler: AppBskyLabelerDefs.LabelerViewDetailed + descriptionRT: RichTextAPI | null + moderationOpts: ModerationOpts + hideBackButton?: boolean + isPlaceholderProfile?: boolean +} + +let ProfileHeaderLabeler = ({ + profile: profileUnshadowed, + labeler, + descriptionRT, + moderationOpts, + hideBackButton = false, + isPlaceholderProfile, +}: Props): React.ReactNode => { + const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> = + useProfileShadow(profileUnshadowed) + const t = useTheme() + const {_} = useLingui() + const {currentAccount, hasSession} = useSession() + const {openModal} = useModalControls() + const {track} = useAnalytics() + const cantSubscribePrompt = Prompt.usePromptControl() + const isSelf = currentAccount?.did === profile.did + + const moderation = useMemo( + () => moderateProfile(profile, moderationOpts), + [profile, moderationOpts], + ) + const {data: preferences} = usePreferencesQuery() + const {mutateAsync: toggleSubscription, variables} = + useLabelerSubscriptionMutation() + const isSubscribed = + variables?.subscribe ?? + preferences?.moderationPrefs.labelers.find(l => l.did === profile.did) + const canSubscribe = + isSubscribed || + (preferences ? preferences?.moderationPrefs.labelers.length < 9 : false) + const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation() + const {mutateAsync: unlikeMod, isPending: isUnlikePending} = + useUnlikeMutation() + const [likeUri, setLikeUri] = React.useState<string>( + labeler.viewer?.like || '', + ) + const [likeCount, setLikeCount] = React.useState(labeler.likeCount || 0) + + const onToggleLiked = React.useCallback(async () => { + if (!labeler) { + return + } + try { + Haptics.default() + + if (likeUri) { + await unlikeMod({uri: likeUri}) + track('CustomFeed:Unlike') + setLikeCount(c => c - 1) + setLikeUri('') + } else { + const res = await likeMod({uri: labeler.uri, cid: labeler.cid}) + track('CustomFeed:Like') + setLikeCount(c => c + 1) + setLikeUri(res.uri) + } + } catch (e: any) { + Toast.show( + _( + msg`There was an an issue contacting the server, please check your internet connection and try again.`, + ), + ) + logger.error(`Failed to toggle labeler like`, {message: e.message}) + } + }, [labeler, likeUri, likeMod, unlikeMod, track, _]) + + const onPressEditProfile = React.useCallback(() => { + track('ProfileHeader:EditProfileButtonClicked') + openModal({ + name: 'edit-profile', + profile, + }) + }, [track, openModal, profile]) + + const onPressSubscribe = React.useCallback(async () => { + if (!canSubscribe) { + cantSubscribePrompt.open() + return + } + try { + await toggleSubscription({ + did: profile.did, + subscribe: !isSubscribed, + }) + } catch (e: any) { + // setSubscriptionError(e.message) + logger.error(`Failed to subscribe to labeler`, {message: e.message}) + } + }, [ + toggleSubscription, + isSubscribed, + profile, + canSubscribe, + cantSubscribePrompt, + ]) + + const isMe = React.useMemo( + () => currentAccount?.did === profile.did, + [currentAccount, profile], + ) + + return ( + <ProfileHeaderShell + profile={profile} + moderation={moderation} + hideBackButton={hideBackButton} + isPlaceholderProfile={isPlaceholderProfile}> + <View style={[a.px_lg, a.pt_md, a.pb_sm]} pointerEvents="box-none"> + <View + style={[a.flex_row, a.justify_end, a.gap_sm, a.pb_lg]} + pointerEvents="box-none"> + {isMe ? ( + <Button + testID="profileHeaderEditProfileButton" + size="small" + color="secondary" + variant="solid" + onPress={onPressEditProfile} + label={_(msg`Edit profile`)} + style={a.rounded_full}> + <ButtonText> + <Trans>Edit Profile</Trans> + </ButtonText> + </Button> + ) : !isAppLabeler(profile.did) ? ( + <> + <Button + testID="toggleSubscribeBtn" + label={ + isSubscribed + ? _(msg`Unsubscribe from this labeler`) + : _(msg`Subscribe to this labeler`) + } + disabled={!hasSession} + onPress={onPressSubscribe}> + {state => ( + <View + style={[ + { + paddingVertical: 12, + backgroundColor: + isSubscribed || !canSubscribe + ? state.hovered || state.pressed + ? t.palette.contrast_50 + : t.palette.contrast_25 + : state.hovered || state.pressed + ? tokens.color.temp_purple_dark + : tokens.color.temp_purple, + }, + a.px_lg, + a.rounded_sm, + a.gap_sm, + ]}> + <Text + style={[ + { + color: canSubscribe + ? isSubscribed + ? t.palette.contrast_700 + : t.palette.white + : t.palette.contrast_400, + }, + a.font_bold, + a.text_center, + ]}> + {isSubscribed ? ( + <Trans>Unsubscribe</Trans> + ) : ( + <Trans>Subscribe to Labeler</Trans> + )} + </Text> + </View> + )} + </Button> + </> + ) : null} + <ProfileMenu profile={profile} /> + </View> + <View style={[a.flex_col, a.gap_xs, a.pb_md]}> + <ProfileHeaderDisplayName profile={profile} moderation={moderation} /> + <ProfileHeaderHandle profile={profile} /> + </View> + {!isPlaceholderProfile && ( + <> + {isSelf && <ProfileHeaderMetrics profile={profile} />} + {descriptionRT && !moderation.ui('profileView').blur ? ( + <View pointerEvents="auto"> + <RichText + testID="profileHeaderDescription" + style={[a.text_md]} + numberOfLines={15} + value={descriptionRT} + /> + </View> + ) : undefined} + {!isAppLabeler(profile.did) && ( + <View style={[a.flex_row, a.gap_xs, a.align_center, a.pt_lg]}> + <Button + testID="toggleLikeBtn" + size="small" + color="secondary" + variant="solid" + shape="round" + label={_(msg`Like this feed`)} + disabled={!hasSession || isLikePending || isUnlikePending} + onPress={onToggleLiked}> + {likeUri ? ( + <HeartFilled fill={t.palette.negative_400} /> + ) : ( + <Heart fill={t.atoms.text_contrast_medium.color} /> + )} + </Button> + + {typeof likeCount === 'number' && ( + <Link + to={{ + screen: 'ProfileLabelerLikedBy', + params: { + name: labeler.creator.handle || labeler.creator.did, + }, + }} + size="tiny" + label={_( + msg`Liked by ${likeCount} ${pluralize( + likeCount, + 'user', + )}`, + )}> + {({hovered, focused, pressed}) => ( + <Text + style={[ + a.font_bold, + a.text_sm, + t.atoms.text_contrast_medium, + (hovered || focused || pressed) && + t.atoms.text_contrast_high, + ]}> + <Trans> + Liked by {likeCount} {pluralize(likeCount, 'user')} + </Trans> + </Text> + )} + </Link> + )} + </View> + )} + </> + )} + </View> + <CantSubscribePrompt control={cantSubscribePrompt} /> + </ProfileHeaderShell> + ) +} +ProfileHeaderLabeler = memo(ProfileHeaderLabeler) +export {ProfileHeaderLabeler} + +function CantSubscribePrompt({ + control, +}: { + control: DialogOuterProps['control'] +}) { + return ( + <Prompt.Outer control={control}> + <Prompt.Title>Unable to subscribe</Prompt.Title> + <Prompt.Description> + <Trans> + We're sorry! You can only subscribe to ten labelers, and you've + reached your limit of ten. + </Trans> + </Prompt.Description> + <Prompt.Actions> + <Prompt.Action onPress={control.close}>OK</Prompt.Action> + </Prompt.Actions> + </Prompt.Outer> + ) +} diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx new file mode 100644 index 000000000..8b9038244 --- /dev/null +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -0,0 +1,286 @@ +import React, {memo, useMemo} from 'react' +import {View} from 'react-native' +import { + AppBskyActorDefs, + ModerationOpts, + moderateProfile, + RichText as RichTextAPI, +} from '@atproto/api' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' + +import {useModalControls} from '#/state/modals' +import {useAnalytics} from 'lib/analytics/analytics' +import {useSession, useRequireAuth} from '#/state/session' +import {Shadow} from '#/state/cache/types' +import {useProfileShadow} from 'state/cache/profile-shadow' +import { + useProfileFollowMutationQueue, + useProfileBlockMutationQueue, +} from '#/state/queries/profile' +import {logger} from '#/logger' +import {sanitizeDisplayName} from 'lib/strings/display-names' + +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText, ButtonIcon} from '#/components/Button' +import * as Toast from '#/view/com/util/Toast' +import {ProfileHeaderShell} from './Shell' +import {ProfileMenu} from '#/view/com/profile/ProfileMenu' +import {ProfileHeaderDisplayName} from './DisplayName' +import {ProfileHeaderHandle} from './Handle' +import {ProfileHeaderMetrics} from './Metrics' +import {ProfileHeaderSuggestedFollows} from '#/view/com/profile/ProfileHeaderSuggestedFollows' +import {RichText} from '#/components/RichText' +import * as Prompt from '#/components/Prompt' +import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' +import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' + +interface Props { + profile: AppBskyActorDefs.ProfileViewDetailed + descriptionRT: RichTextAPI | null + moderationOpts: ModerationOpts + hideBackButton?: boolean + isPlaceholderProfile?: boolean +} + +let ProfileHeaderStandard = ({ + profile: profileUnshadowed, + descriptionRT, + moderationOpts, + hideBackButton = false, + isPlaceholderProfile, +}: Props): React.ReactNode => { + const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> = + useProfileShadow(profileUnshadowed) + const t = useTheme() + const {currentAccount, hasSession} = useSession() + const {_} = useLingui() + const {openModal} = useModalControls() + const {track} = useAnalytics() + const moderation = useMemo( + () => moderateProfile(profile, moderationOpts), + [profile, moderationOpts], + ) + const [showSuggestedFollows, setShowSuggestedFollows] = React.useState(false) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'ProfileHeader', + ) + const [_queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) + const unblockPromptControl = Prompt.usePromptControl() + const requireAuth = useRequireAuth() + + const onPressEditProfile = React.useCallback(() => { + track('ProfileHeader:EditProfileButtonClicked') + openModal({ + name: 'edit-profile', + profile, + }) + }, [track, openModal, profile]) + + const onPressFollow = () => { + requireAuth(async () => { + try { + track('ProfileHeader:FollowButtonClicked') + await queueFollow() + Toast.show( + _( + msg`Following ${sanitizeDisplayName( + profile.displayName || profile.handle, + moderation.ui('displayName'), + )}`, + ), + ) + } catch (e: any) { + if (e?.name !== 'AbortError') { + logger.error('Failed to follow', {message: String(e)}) + Toast.show(_(msg`There was an issue! ${e.toString()}`)) + } + } + }) + } + + const onPressUnfollow = () => { + requireAuth(async () => { + try { + track('ProfileHeader:UnfollowButtonClicked') + await queueUnfollow() + Toast.show( + _( + msg`No longer following ${sanitizeDisplayName( + profile.displayName || profile.handle, + moderation.ui('displayName'), + )}`, + ), + ) + } catch (e: any) { + if (e?.name !== 'AbortError') { + logger.error('Failed to unfollow', {message: String(e)}) + Toast.show(_(msg`There was an issue! ${e.toString()}`)) + } + } + }) + } + + const unblockAccount = React.useCallback(async () => { + track('ProfileHeader:UnblockAccountButtonClicked') + try { + await queueUnblock() + Toast.show(_(msg`Account unblocked`)) + } catch (e: any) { + if (e?.name !== 'AbortError') { + logger.error('Failed to unblock account', {message: e}) + Toast.show(_(msg`There was an issue! ${e.toString()}`)) + } + } + }, [_, queueUnblock, track]) + + const isMe = React.useMemo( + () => currentAccount?.did === profile.did, + [currentAccount, profile], + ) + + return ( + <ProfileHeaderShell + profile={profile} + moderation={moderation} + hideBackButton={hideBackButton} + isPlaceholderProfile={isPlaceholderProfile}> + <View style={[a.px_lg, a.pt_md, a.pb_sm]} pointerEvents="box-none"> + <View + style={[a.flex_row, a.justify_end, a.gap_sm, a.pb_sm]} + pointerEvents="box-none"> + {isMe ? ( + <Button + testID="profileHeaderEditProfileButton" + size="small" + color="secondary" + variant="solid" + onPress={onPressEditProfile} + label={_(msg`Edit profile`)} + style={a.rounded_full}> + <ButtonText> + <Trans>Edit Profile</Trans> + </ButtonText> + </Button> + ) : profile.viewer?.blocking ? ( + profile.viewer?.blockingByList ? null : ( + <Button + testID="unblockBtn" + size="small" + color="secondary" + variant="solid" + label={_(msg`Unblock`)} + disabled={!hasSession} + onPress={() => unblockPromptControl.open()} + style={a.rounded_full}> + <ButtonText> + <Trans context="action">Unblock</Trans> + </ButtonText> + </Button> + ) + ) : !profile.viewer?.blockedBy ? ( + <> + {hasSession && ( + <Button + testID="suggestedFollowsBtn" + size="small" + color={showSuggestedFollows ? 'primary' : 'secondary'} + variant="solid" + shape="round" + onPress={() => setShowSuggestedFollows(!showSuggestedFollows)} + label={_(msg`Show follows similar to ${profile.handle}`)}> + <FontAwesomeIcon + icon="user-plus" + style={ + showSuggestedFollows + ? {color: t.palette.white} + : t.atoms.text + } + size={14} + /> + </Button> + )} + + <Button + testID={profile.viewer?.following ? 'unfollowBtn' : 'followBtn'} + size="small" + color={profile.viewer?.following ? 'secondary' : 'primary'} + variant="solid" + label={ + profile.viewer?.following + ? _(msg`Unfollow ${profile.handle}`) + : _(msg`Follow ${profile.handle}`) + } + disabled={!hasSession} + onPress={ + profile.viewer?.following ? onPressUnfollow : onPressFollow + } + style={[a.rounded_full, a.gap_xs]}> + <ButtonIcon + position="left" + icon={profile.viewer?.following ? Check : Plus} + /> + <ButtonText> + {profile.viewer?.following ? ( + <Trans>Following</Trans> + ) : ( + <Trans>Follow</Trans> + )} + </ButtonText> + </Button> + </> + ) : null} + <ProfileMenu profile={profile} /> + </View> + <View style={[a.flex_col, a.gap_xs, a.pb_sm]}> + <ProfileHeaderDisplayName profile={profile} moderation={moderation} /> + <ProfileHeaderHandle profile={profile} /> + </View> + {!isPlaceholderProfile && ( + <> + <ProfileHeaderMetrics profile={profile} /> + {descriptionRT && !moderation.ui('profileView').blur ? ( + <View pointerEvents="auto"> + <RichText + testID="profileHeaderDescription" + style={[a.text_md]} + numberOfLines={15} + value={descriptionRT} + /> + </View> + ) : undefined} + </> + )} + </View> + {showSuggestedFollows && ( + <ProfileHeaderSuggestedFollows + actorDid={profile.did} + requestDismiss={() => { + if (showSuggestedFollows) { + setShowSuggestedFollows(false) + } else { + track('ProfileHeader:SuggestedFollowsOpened') + setShowSuggestedFollows(true) + } + }} + /> + )} + <Prompt.Basic + control={unblockPromptControl} + title={_(msg`Unblock Account?`)} + description={_( + msg`The account will be able to interact with you after unblocking.`, + )} + onConfirm={unblockAccount} + confirmButtonCta={ + profile.viewer?.blocking ? _(msg`Unblock`) : _(msg`Block`) + } + confirmButtonColor="negative" + /> + </ProfileHeaderShell> + ) +} +ProfileHeaderStandard = memo(ProfileHeaderStandard) +export {ProfileHeaderStandard} diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx new file mode 100644 index 000000000..c470cb286 --- /dev/null +++ b/src/screens/Profile/Header/Shell.tsx @@ -0,0 +1,164 @@ +import React, {memo} from 'react' +import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {useNavigation} from '@react-navigation/native' +import {AppBskyActorDefs, ModerationDecision} from '@atproto/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {NavigationProp} from 'lib/routes/types' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {BACK_HITSLOP} from 'lib/constants' +import {useSession} from '#/state/session' +import {Shadow} from '#/state/cache/types' +import {useLightboxControls, ProfileImageLightbox} from '#/state/lightbox' + +import {atoms as a, useTheme} from '#/alf' +import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' +import {BlurView} from 'view/com/util/BlurView' +import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {UserBanner} from 'view/com/util/UserBanner' +import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' + +interface Props { + profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> + moderation: ModerationDecision + hideBackButton?: boolean + isPlaceholderProfile?: boolean +} + +let ProfileHeaderShell = ({ + children, + profile, + moderation, + hideBackButton = false, + isPlaceholderProfile, +}: React.PropsWithChildren<Props>): React.ReactNode => { + const t = useTheme() + const {currentAccount} = useSession() + const {_} = useLingui() + const {openLightbox} = useLightboxControls() + const navigation = useNavigation<NavigationProp>() + const {isDesktop} = useWebMediaQueries() + + const onPressBack = React.useCallback(() => { + if (navigation.canGoBack()) { + navigation.goBack() + } else { + navigation.navigate('Home') + } + }, [navigation]) + + const onPressAvi = React.useCallback(() => { + const modui = moderation.ui('avatar') + if (profile.avatar && !(modui.blur && modui.noOverride)) { + openLightbox(new ProfileImageLightbox(profile)) + } + }, [openLightbox, profile, moderation]) + + const isMe = React.useMemo( + () => currentAccount?.did === profile.did, + [currentAccount, profile], + ) + + return ( + <View style={t.atoms.bg} pointerEvents="box-none"> + <View pointerEvents="none"> + {isPlaceholderProfile ? ( + <LoadingPlaceholder + width="100%" + height={150} + style={{borderRadius: 0}} + /> + ) : ( + <UserBanner + type={profile.associated?.labeler ? 'labeler' : 'default'} + banner={profile.banner} + moderation={moderation.ui('banner')} + /> + )} + </View> + + {children} + + <View style={[a.px_lg, a.pb_sm]} pointerEvents="box-none"> + <ProfileHeaderAlerts moderation={moderation} /> + {isMe && ( + <LabelsOnMe details={{did: profile.did}} labels={profile.labels} /> + )} + </View> + + {!isDesktop && !hideBackButton && ( + <TouchableWithoutFeedback + testID="profileHeaderBackBtn" + onPress={onPressBack} + hitSlop={BACK_HITSLOP} + accessibilityRole="button" + accessibilityLabel={_(msg`Back`)} + accessibilityHint=""> + <View style={styles.backBtnWrapper}> + <BlurView style={styles.backBtn} blurType="dark"> + <FontAwesomeIcon size={18} icon="angle-left" color="white" /> + </BlurView> + </View> + </TouchableWithoutFeedback> + )} + <TouchableWithoutFeedback + testID="profileHeaderAviButton" + onPress={onPressAvi} + accessibilityRole="image" + accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)} + accessibilityHint=""> + <View + style={[ + t.atoms.bg, + {borderColor: t.atoms.bg.backgroundColor}, + styles.avi, + profile.associated?.labeler && styles.aviLabeler, + ]}> + <UserAvatar + type={profile.associated?.labeler ? 'labeler' : 'user'} + size={90} + avatar={profile.avatar} + moderation={moderation.ui('avatar')} + /> + </View> + </TouchableWithoutFeedback> + </View> + ) +} +ProfileHeaderShell = memo(ProfileHeaderShell) +export {ProfileHeaderShell} + +const styles = StyleSheet.create({ + backBtnWrapper: { + position: 'absolute', + top: 10, + left: 10, + width: 30, + height: 30, + overflow: 'hidden', + borderRadius: 15, + // @ts-ignore web only + cursor: 'pointer', + }, + backBtn: { + width: 30, + height: 30, + borderRadius: 15, + alignItems: 'center', + justifyContent: 'center', + }, + avi: { + position: 'absolute', + top: 110, + left: 10, + width: 94, + height: 94, + borderRadius: 47, + borderWidth: 2, + }, + aviLabeler: { + borderRadius: 10, + }, +}) diff --git a/src/screens/Profile/Header/index.tsx b/src/screens/Profile/Header/index.tsx new file mode 100644 index 000000000..1280dd8b1 --- /dev/null +++ b/src/screens/Profile/Header/index.tsx @@ -0,0 +1,78 @@ +import React, {memo} from 'react' +import {StyleSheet, View} from 'react-native' +import { + AppBskyActorDefs, + AppBskyLabelerDefs, + ModerationOpts, + RichText as RichTextAPI, +} from '@atproto/api' +import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' +import {usePalette} from 'lib/hooks/usePalette' + +import {ProfileHeaderStandard} from './ProfileHeaderStandard' +import {ProfileHeaderLabeler} from './ProfileHeaderLabeler' + +let ProfileHeaderLoading = (_props: {}): React.ReactNode => { + const pal = usePalette('default') + return ( + <View style={pal.view}> + <LoadingPlaceholder width="100%" height={150} style={{borderRadius: 0}} /> + <View + style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}> + <LoadingPlaceholder width={80} height={80} style={styles.br40} /> + </View> + <View style={styles.content}> + <View style={[styles.buttonsLine]}> + <LoadingPlaceholder width={167} height={31} style={styles.br50} /> + </View> + </View> + </View> + ) +} +ProfileHeaderLoading = memo(ProfileHeaderLoading) +export {ProfileHeaderLoading} + +interface Props { + profile: AppBskyActorDefs.ProfileViewDetailed + labeler: AppBskyLabelerDefs.LabelerViewDetailed | undefined + descriptionRT: RichTextAPI | null + moderationOpts: ModerationOpts + hideBackButton?: boolean + isPlaceholderProfile?: boolean +} + +let ProfileHeader = (props: Props): React.ReactNode => { + if (props.profile.associated?.labeler) { + if (!props.labeler) { + return <ProfileHeaderLoading /> + } + return <ProfileHeaderLabeler {...props} labeler={props.labeler} /> + } + return <ProfileHeaderStandard {...props} /> +} +ProfileHeader = memo(ProfileHeader) +export {ProfileHeader} + +const styles = StyleSheet.create({ + avi: { + position: 'absolute', + top: 110, + left: 10, + width: 84, + height: 84, + borderRadius: 42, + borderWidth: 2, + }, + content: { + paddingTop: 8, + paddingHorizontal: 14, + paddingBottom: 4, + }, + buttonsLine: { + flexDirection: 'row', + marginLeft: 'auto', + marginBottom: 12, + }, + br40: {borderRadius: 40}, + br50: {borderRadius: 50}, +}) diff --git a/src/screens/Profile/ProfileLabelerLikedBy.tsx b/src/screens/Profile/ProfileLabelerLikedBy.tsx new file mode 100644 index 000000000..1d2167520 --- /dev/null +++ b/src/screens/Profile/ProfileLabelerLikedBy.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import {View} from 'react-native' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useFocusEffect} from '@react-navigation/native' + +import {NativeStackScreenProps, CommonNavigatorParams} from '#/lib/routes/types' +import {ViewHeader} from '#/view/com/util/ViewHeader' +import {LikedByList} from '#/components/LikedByList' +import {useSetMinimalShellMode} from '#/state/shell' +import {makeRecordUri} from '#/lib/strings/url-helpers' + +import {atoms as a, useBreakpoints} from '#/alf' + +export function ProfileLabelerLikedByScreen({ + route, +}: NativeStackScreenProps<CommonNavigatorParams, 'ProfileLabelerLikedBy'>) { + const setMinimalShellMode = useSetMinimalShellMode() + const {name: handleOrDid} = route.params + const uri = makeRecordUri(handleOrDid, 'app.bsky.labeler.service', 'self') + const {_} = useLingui() + const {gtMobile} = useBreakpoints() + + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) + + return ( + <View + style={[ + a.mx_auto, + a.w_full, + a.h_full_vh, + gtMobile && [ + { + maxWidth: 600, + }, + ], + ]}> + <ViewHeader title={_(msg`Liked By`)} /> + <LikedByList uri={uri} /> + </View> + ) +} diff --git a/src/screens/Profile/Sections/Feed.tsx b/src/screens/Profile/Sections/Feed.tsx new file mode 100644 index 000000000..0a5e2208d --- /dev/null +++ b/src/screens/Profile/Sections/Feed.tsx @@ -0,0 +1,88 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {ListRef} from 'view/com/util/List' +import {Feed} from 'view/com/posts/Feed' +import {EmptyState} from 'view/com/util/EmptyState' +import {FeedDescriptor} from '#/state/queries/post-feed' +import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' +import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' +import {useQueryClient} from '@tanstack/react-query' +import {truncateAndInvalidate} from '#/state/queries/util' +import {Text} from '#/view/com/util/text/Text' +import {usePalette} from 'lib/hooks/usePalette' +import {isNative} from '#/platform/detection' +import {SectionRef} from './types' + +interface FeedSectionProps { + feed: FeedDescriptor + headerHeight: number + isFocused: boolean + scrollElRef: ListRef + ignoreFilterFor?: string +} +export const ProfileFeedSection = React.forwardRef< + SectionRef, + FeedSectionProps +>(function FeedSectionImpl( + {feed, headerHeight, isFocused, scrollElRef, ignoreFilterFor}, + ref, +) { + const {_} = useLingui() + const queryClient = useQueryClient() + const [hasNew, setHasNew] = React.useState(false) + const [isScrolledDown, setIsScrolledDown] = React.useState(false) + + const onScrollToTop = React.useCallback(() => { + scrollElRef.current?.scrollToOffset({ + animated: isNative, + offset: -headerHeight, + }) + truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) + setHasNew(false) + }, [scrollElRef, headerHeight, queryClient, feed, setHasNew]) + React.useImperativeHandle(ref, () => ({ + scrollToTop: onScrollToTop, + })) + + const renderPostsEmpty = React.useCallback(() => { + return <EmptyState icon="feed" message={_(msg`This feed is empty!`)} /> + }, [_]) + + return ( + <View> + <Feed + testID="postsFeed" + enabled={isFocused} + feed={feed} + scrollElRef={scrollElRef} + onHasNew={setHasNew} + onScrolledDownChange={setIsScrolledDown} + renderEmptyState={renderPostsEmpty} + headerOffset={headerHeight} + renderEndOfFeed={ProfileEndOfFeed} + ignoreFilterFor={ignoreFilterFor} + /> + {(isScrolledDown || hasNew) && ( + <LoadLatestBtn + onPress={onScrollToTop} + label={_(msg`Load new posts`)} + showIndicator={hasNew} + /> + )} + </View> + ) +}) + +function ProfileEndOfFeed() { + const pal = usePalette('default') + + return ( + <View style={[pal.border, {paddingTop: 32, borderTopWidth: 1}]}> + <Text style={[pal.textLight, pal.border, {textAlign: 'center'}]}> + <Trans>End of feed</Trans> + </Text> + </View> + ) +} diff --git a/src/screens/Profile/Sections/Labels.tsx b/src/screens/Profile/Sections/Labels.tsx new file mode 100644 index 000000000..08db4a861 --- /dev/null +++ b/src/screens/Profile/Sections/Labels.tsx @@ -0,0 +1,215 @@ +import React from 'react' +import {View} from 'react-native' +import { + AppBskyLabelerDefs, + ModerationOpts, + interpretLabelValueDefinitions, + InterpretedLabelValueDefinition, +} from '@atproto/api' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useSafeAreaFrame} from 'react-native-safe-area-context' + +import {useScrollHandlers} from '#/lib/ScrollContext' +import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' +import {isLabelerSubscribed, lookupLabelValueDefinition} from '#/lib/moderation' +import {ListRef} from '#/view/com/util/List' +import {SectionRef} from './types' +import {isNative} from '#/platform/detection' + +import {useTheme, atoms as a} from '#/alf' +import {Text} from '#/components/Typography' +import {Loader} from '#/components/Loader' +import {Divider} from '#/components/Divider' +import {CenteredView, ScrollView} from '#/view/com/util/Views' +import {ErrorState} from '../ErrorState' +import {ModerationLabelPref} from '#/components/moderation/ModerationLabelPref' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' + +interface LabelsSectionProps { + isLabelerLoading: boolean + labelerInfo: AppBskyLabelerDefs.LabelerViewDetailed | undefined + labelerError: Error | null + moderationOpts: ModerationOpts + scrollElRef: ListRef + headerHeight: number +} +export const ProfileLabelsSection = React.forwardRef< + SectionRef, + LabelsSectionProps +>(function LabelsSectionImpl( + { + isLabelerLoading, + labelerInfo, + labelerError, + moderationOpts, + scrollElRef, + headerHeight, + }, + ref, +) { + const {_} = useLingui() + const {height: minHeight} = useSafeAreaFrame() + + const onScrollToTop = React.useCallback(() => { + // @ts-ignore TODO fix this + scrollElRef.current?.scrollTo({ + animated: isNative, + x: 0, + y: -headerHeight, + }) + }, [scrollElRef, headerHeight]) + + React.useImperativeHandle(ref, () => ({ + scrollToTop: onScrollToTop, + })) + + return ( + <CenteredView style={{flex: 1, minHeight}} sideBorders> + {isLabelerLoading ? ( + <View style={[a.w_full, a.align_center]}> + <Loader size="xl" /> + </View> + ) : labelerError || !labelerInfo ? ( + <ErrorState + error={ + labelerError?.toString() || + _(msg`Something went wrong, please try again.`) + } + /> + ) : ( + <ProfileLabelsSectionInner + moderationOpts={moderationOpts} + labelerInfo={labelerInfo} + scrollElRef={scrollElRef} + headerHeight={headerHeight} + /> + )} + </CenteredView> + ) +}) + +export function ProfileLabelsSectionInner({ + moderationOpts, + labelerInfo, + scrollElRef, + headerHeight, +}: { + moderationOpts: ModerationOpts + labelerInfo: AppBskyLabelerDefs.LabelerViewDetailed + scrollElRef: ListRef + headerHeight: number +}) { + const t = useTheme() + const contextScrollHandlers = useScrollHandlers() + + const scrollHandler = useAnimatedScrollHandler({ + onBeginDrag(e, ctx) { + contextScrollHandlers.onBeginDrag?.(e, ctx) + }, + onEndDrag(e, ctx) { + contextScrollHandlers.onEndDrag?.(e, ctx) + }, + onScroll(e, ctx) { + contextScrollHandlers.onScroll?.(e, ctx) + }, + }) + + const {labelValues} = labelerInfo.policies + const isSubscribed = isLabelerSubscribed(labelerInfo, moderationOpts) + const labelDefs = React.useMemo(() => { + const customDefs = interpretLabelValueDefinitions(labelerInfo) + return labelValues + .map(val => lookupLabelValueDefinition(val, customDefs)) + .filter( + def => def && def?.configurable, + ) as InterpretedLabelValueDefinition[] + }, [labelerInfo, labelValues]) + + return ( + <ScrollView + // @ts-ignore TODO fix this + ref={scrollElRef} + scrollEventThrottle={1} + contentContainerStyle={{ + paddingTop: headerHeight, + borderWidth: 0, + }} + contentOffset={{x: 0, y: headerHeight * -1}} + onScroll={scrollHandler}> + <View style={[a.pt_xl, a.px_lg, a.border_t, t.atoms.border_contrast_low]}> + <View> + <Text style={[t.atoms.text_contrast_high, a.leading_snug, a.text_sm]}> + <Trans> + Labels are annotations on users and content. They can be used to + hide, warn, and categorize the network. + </Trans> + </Text> + {labelerInfo.creator.viewer?.blocking ? ( + <View style={[a.flex_row, a.gap_sm, a.align_center, a.mt_md]}> + <CircleInfo size="sm" fill={t.atoms.text_contrast_medium.color} /> + <Text + style={[t.atoms.text_contrast_high, a.leading_snug, a.text_sm]}> + <Trans> + Blocking does not prevent this labeler from placing labels on + your account. + </Trans> + </Text> + </View> + ) : null} + {labelValues.length === 0 ? ( + <Text + style={[ + a.pt_xl, + t.atoms.text_contrast_high, + a.leading_snug, + a.text_sm, + ]}> + <Trans> + This labeler hasn't declared what labels it publishes, and may + not be active. + </Trans> + </Text> + ) : !isSubscribed ? ( + <Text + style={[ + a.pt_xl, + t.atoms.text_contrast_high, + a.leading_snug, + a.text_sm, + ]}> + <Trans> + Subscribe to @{labelerInfo.creator.handle} to use these labels: + </Trans> + </Text> + ) : null} + </View> + {labelDefs.length > 0 && ( + <View + style={[ + a.mt_xl, + a.w_full, + a.rounded_md, + a.overflow_hidden, + t.atoms.bg_contrast_25, + ]}> + {labelDefs.map((labelDef, i) => { + return ( + <React.Fragment key={labelDef.identifier}> + {i !== 0 && <Divider />} + <ModerationLabelPref + disabled={isSubscribed ? undefined : true} + labelValueDefinition={labelDef} + labelerDid={labelerInfo.creator.did} + /> + </React.Fragment> + ) + })} + </View> + )} + + <View style={{height: 400}} /> + </View> + </ScrollView> + ) +} diff --git a/src/screens/Profile/Sections/types.ts b/src/screens/Profile/Sections/types.ts new file mode 100644 index 000000000..a7f77d648 --- /dev/null +++ b/src/screens/Profile/Sections/types.ts @@ -0,0 +1,3 @@ +export interface SectionRef { + scrollToTop: () => void +} diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index db5be0b8d..524dcb1ba 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {AppBskyActorDefs, AppBskyGraphDefs, ModerationUI} from '@atproto/api' +import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api' import {Image as RNImage} from 'react-native-image-crop-picker' import {ImageModel} from '#/state/models/media/image' @@ -14,32 +14,6 @@ export interface EditProfileModal { onUpdate?: () => void } -export interface ModerationDetailsModal { - name: 'moderation-details' - context: 'account' | 'content' - moderation: ModerationUI -} - -export type ReportModal = { - name: 'report' -} & ( - | { - uri: string - cid: string - } - | {did: string} -) - -export type AppealLabelModal = { - name: 'appeal-label' -} & ( - | { - uri: string - cid: string - } - | {did: string} -) - export interface CreateOrEditListModal { name: 'create-or-edit-list' purpose?: string @@ -123,10 +97,6 @@ export interface AddAppPasswordModal { name: 'add-app-password' } -export interface ContentFilteringSettingsModal { - name: 'content-filtering-settings' -} - export interface ContentLanguagesSettingsModal { name: 'content-languages-settings' } @@ -181,15 +151,9 @@ export type Modal = | SwitchAccountModal // Curation - | ContentFilteringSettingsModal | ContentLanguagesSettingsModal | PostLanguagesSettingsModal - // Moderation - | ModerationDetailsModal - | ReportModal - | AppealLabelModal - // Lists | CreateOrEditListModal | UserAddRemoveListsModal diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index a442b763a..cf1d90151 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -15,6 +15,7 @@ export { useSetExternalEmbedPref, } from './external-embeds-prefs' export * from './hidden-posts' +export {useLabelDefinitions} from './label-defs' export function Provider({children}: React.PropsWithChildren<{}>) { return ( diff --git a/src/state/preferences/label-defs.tsx b/src/state/preferences/label-defs.tsx new file mode 100644 index 000000000..d60f8ccb8 --- /dev/null +++ b/src/state/preferences/label-defs.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import {InterpretedLabelValueDefinition, AppBskyLabelerDefs} from '@atproto/api' +import {useLabelDefinitionsQuery} from '../queries/preferences' + +interface StateContext { + labelDefs: Record<string, InterpretedLabelValueDefinition[]> + labelers: AppBskyLabelerDefs.LabelerViewDetailed[] +} + +const stateContext = React.createContext<StateContext>({ + labelDefs: {}, + labelers: [], +}) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const {labelDefs, labelers} = useLabelDefinitionsQuery() + + const state = {labelDefs, labelers} + + return <stateContext.Provider value={state}>{children}</stateContext.Provider> +} + +export function useLabelDefinitions() { + return React.useContext(stateContext) +} diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts index 3159ad7aa..e6bf04ba3 100644 --- a/src/state/queries/actor-autocomplete.ts +++ b/src/state/queries/actor-autocomplete.ts @@ -6,17 +6,14 @@ import {logger} from '#/logger' import {getAgent} from '#/state/session' import {useMyFollowsQuery} from '#/state/queries/my-follows' import {STALE} from '#/state/queries' -import { - DEFAULT_LOGGED_OUT_PREFERENCES, - getModerationOpts, - useModerationOpts, -} from './preferences' +import {DEFAULT_LOGGED_OUT_PREFERENCES, useModerationOpts} from './preferences' import {isInvalidHandle} from '#/lib/strings/handles' +import {isJustAMute} from '#/lib/moderation' -const DEFAULT_MOD_OPTS = getModerationOpts({ - userDid: '', - preferences: DEFAULT_LOGGED_OUT_PREFERENCES, -}) +const DEFAULT_MOD_OPTS = { + userDid: undefined, + prefs: DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs, +} export const RQKEY = (prefix: string) => ['actor-autocomplete', prefix] @@ -104,18 +101,12 @@ function computeSuggestions( } for (const item of searched) { if (!items.find(item2 => item2.handle === item.handle)) { - items.push({ - did: item.did, - handle: item.handle, - displayName: item.displayName, - avatar: item.avatar, - labels: item.labels, - }) + items.push(item) } } return items.filter(profile => { - const mod = moderateProfile(profile, moderationOpts) - return !mod.account.filter && mod.account.cause?.type !== 'muted' + const modui = moderateProfile(profile, moderationOpts).ui('profileList') + return !modui.filter || isJustAMute(modui) }) } diff --git a/src/state/queries/labeler.ts b/src/state/queries/labeler.ts new file mode 100644 index 000000000..b2f93c4a4 --- /dev/null +++ b/src/state/queries/labeler.ts @@ -0,0 +1,89 @@ +import {z} from 'zod' +import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query' +import {AppBskyLabelerDefs} from '@atproto/api' + +import {getAgent} from '#/state/session' +import {preferencesQueryKey} from '#/state/queries/preferences' +import {STALE} from '#/state/queries' + +export const labelerInfoQueryKey = (did: string) => ['labeler-info', did] +export const labelersInfoQueryKey = (dids: string[]) => [ + 'labelers-info', + dids.sort(), +] +export const labelersDetailedInfoQueryKey = (dids: string[]) => [ + 'labelers-detailed-info', + dids, +] + +export function useLabelerInfoQuery({ + did, + enabled, +}: { + did?: string + enabled?: boolean +}) { + return useQuery({ + enabled: !!did && enabled !== false, + queryKey: labelerInfoQueryKey(did as string), + queryFn: async () => { + const res = await getAgent().app.bsky.labeler.getServices({ + dids: [did as string], + detailed: true, + }) + return res.data.views[0] as AppBskyLabelerDefs.LabelerViewDetailed + }, + }) +} + +export function useLabelersInfoQuery({dids}: {dids: string[]}) { + return useQuery({ + enabled: !!dids.length, + queryKey: labelersInfoQueryKey(dids), + queryFn: async () => { + const res = await getAgent().app.bsky.labeler.getServices({dids}) + return res.data.views as AppBskyLabelerDefs.LabelerView[] + }, + }) +} + +export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) { + return useQuery({ + enabled: !!dids.length, + queryKey: labelersDetailedInfoQueryKey(dids), + gcTime: 1000 * 60 * 60 * 6, // 6 hours + staleTime: STALE.MINUTES.ONE, + queryFn: async () => { + const res = await getAgent().app.bsky.labeler.getServices({ + dids, + detailed: true, + }) + return res.data.views as AppBskyLabelerDefs.LabelerViewDetailed[] + }, + }) +} + +export function useLabelerSubscriptionMutation() { + const queryClient = useQueryClient() + + return useMutation({ + async mutationFn({did, subscribe}: {did: string; subscribe: boolean}) { + // TODO + z.object({ + did: z.string(), + subscribe: z.boolean(), + }).parse({did, subscribe}) + + if (subscribe) { + await getAgent().addLabeler(did) + } else { + await getAgent().removeLabeler(did) + } + }, + onSuccess() { + queryClient.invalidateQueries({ + queryKey: preferencesQueryKey, + }) + }, + }) +} diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts index 626d3e911..97fc57dc1 100644 --- a/src/state/queries/notifications/util.ts +++ b/src/state/queries/notifications/util.ts @@ -1,14 +1,13 @@ import { AppBskyNotificationListNotifications, ModerationOpts, - moderateProfile, + moderateNotification, AppBskyFeedDefs, AppBskyFeedPost, AppBskyFeedRepost, AppBskyFeedLike, AppBskyEmbedRecord, } from '@atproto/api' -import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' import chunk from 'lodash.chunk' import {QueryClient} from '@tanstack/react-query' import {getAgent} from '../../session' @@ -88,37 +87,20 @@ export async function fetchPage({ // internal methods // = -// TODO this should be in the sdk as moderateNotification -prf -function shouldFilterNotif( +export function shouldFilterNotif( notif: AppBskyNotificationListNotifications.Notification, moderationOpts: ModerationOpts | undefined, ): boolean { if (!moderationOpts) { return false } - const profile = moderateProfile(notif.author, moderationOpts) - if ( - profile.account.filter || - profile.profile.filter || - notif.author.viewer?.muted - ) { - return true - } - if ( - notif.type === 'reply' || - notif.type === 'quote' || - notif.type === 'mention' - ) { - // NOTE: the notification overlaps the post enough for this to work - const post = moderatePost(notif, moderationOpts) - if (post.content.filter) { - return true - } + if (notif.author.viewer?.following) { + return false } - return false + return moderateNotification(notif, moderationOpts).ui('contentList').filter } -function groupNotifications( +export function groupNotifications( notifs: AppBskyNotificationListNotifications.Notification[], ): FeedNotification[] { const groupedNotifs: FeedNotification[] = [] diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index c295ffcb0..0e6eef52c 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -3,8 +3,8 @@ import {AppState} from 'react-native' import { AppBskyFeedDefs, AppBskyFeedPost, + ModerationDecision, AtUri, - PostModeration, } from '@atproto/api' import { useInfiniteQuery, @@ -29,7 +29,6 @@ import {STALE} from '#/state/queries' import {precacheFeedPostProfiles} from './profile' import {getAgent} from '#/state/session' import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const' -import {getModerationOpts} from '#/state/queries/preferences/moderation' import {KnownError} from '#/view/com/posts/FeedErrorMessage' import {embedViewRecordToPostView, getEmbeddedPost} from './util' import {useModerationOpts} from './preferences' @@ -69,7 +68,7 @@ export interface FeedPostSliceItem { post: AppBskyFeedDefs.PostView record: AppBskyFeedPost.Record reason?: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource - moderation: PostModeration + moderation: ModerationDecision } export interface FeedPostSlice { @@ -250,9 +249,17 @@ export function usePostFeedQuery( // apply moderation filter for (let i = 0; i < slice.items.length; i++) { + const ignoreFilter = + slice.items[i].post.author.did === ignoreFilterFor + if (ignoreFilter) { + // remove mutes to avoid confused UIs + moderations[i].causes = moderations[i].causes.filter( + cause => cause.type !== 'muted', + ) + } if ( - moderations[i]?.content.filter && - slice.items[i].post.author.did !== ignoreFilterFor + !ignoreFilter && + moderations[i]?.ui('contentList').filter ) { return undefined } @@ -435,13 +442,12 @@ function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) { let somePostsPassModeration = false for (const item of feed) { - const moderationOpts = getModerationOpts({ - userDid: '', - preferences: DEFAULT_LOGGED_OUT_PREFERENCES, + const moderation = moderatePost(item.post, { + userDid: undefined, + prefs: DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs, }) - const moderation = moderatePost(item.post, moderationOpts) - if (!moderation.content.filter) { + if (!moderation.ui('contentList').filter) { // we have a sfw post somePostsPassModeration = true } diff --git a/src/state/queries/post-liked-by.ts b/src/state/queries/post-liked-by.ts index 2cde07f28..a0498ada4 100644 --- a/src/state/queries/post-liked-by.ts +++ b/src/state/queries/post-liked-by.ts @@ -12,9 +12,9 @@ const PAGE_SIZE = 30 type RQPageParam = string | undefined // TODO refactor invalidate on mutate? -export const RQKEY = (resolvedUri: string) => ['post-liked-by', resolvedUri] +export const RQKEY = (resolvedUri: string) => ['liked-by', resolvedUri] -export function usePostLikedByQuery(resolvedUri: string | undefined) { +export function useLikedByQuery(resolvedUri: string | undefined) { return useInfiniteQuery< AppBskyFeedGetLikes.OutputSchema, Error, diff --git a/src/state/queries/preferences/const.ts b/src/state/queries/preferences/const.ts index 53c9e482a..4cb4d1e96 100644 --- a/src/state/queries/preferences/const.ts +++ b/src/state/queries/preferences/const.ts @@ -29,26 +29,20 @@ export const DEFAULT_PROD_FEEDS = { export const DEFAULT_LOGGED_OUT_PREFERENCES: UsePreferencesQueryResponse = { birthDate: new Date('2022-11-17'), // TODO(pwi) - adultContentEnabled: false, feeds: { saved: [], pinned: [], unpinned: [], }, - // labels are undefined until set by user - contentLabels: { - nsfw: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.nsfw, - nudity: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.nudity, - suggestive: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.suggestive, - gore: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.gore, - hate: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.hate, - spam: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.spam, - impersonation: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES.impersonation, + moderationPrefs: { + adultContentEnabled: false, + labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES, + labelers: [], + mutedWords: [], + hiddenPosts: [], }, feedViewPrefs: DEFAULT_HOME_FEED_PREFS, threadViewPrefs: DEFAULT_THREAD_VIEW_PREFS, userAge: 13, // TODO(pwi) interests: {tags: []}, - mutedWords: [], - hiddenPosts: [], } diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts index 37ef10ae0..f9cd59cda 100644 --- a/src/state/queries/preferences/index.ts +++ b/src/state/queries/preferences/index.ts @@ -1,29 +1,29 @@ -import {useMemo} from 'react' +import {useMemo, createContext, useContext} from 'react' import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query' import { LabelPreference, BskyFeedViewPreference, + ModerationOpts, AppBskyActorDefs, + BSKY_LABELER_DID, } from '@atproto/api' import {track} from '#/lib/analytics/analytics' import {getAge} from '#/lib/strings/time' -import {useSession, getAgent} from '#/state/session' -import {DEFAULT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation' +import {getAgent, useSession} from '#/state/session' import { - ConfigurableLabelGroup, UsePreferencesQueryResponse, ThreadViewPreferences, } from '#/state/queries/preferences/types' -import {temp__migrateLabelPref} from '#/state/queries/preferences/util' import { DEFAULT_HOME_FEED_PREFS, DEFAULT_THREAD_VIEW_PREFS, DEFAULT_LOGGED_OUT_PREFERENCES, } from '#/state/queries/preferences/const' -import {getModerationOpts} from '#/state/queries/preferences/moderation' +import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation' import {STALE} from '#/state/queries' -import {useHiddenPosts} from '#/state/preferences/hidden-posts' +import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences' +import {saveLabelers} from '#/state/session/agent-config' export * from '#/state/queries/preferences/types' export * from '#/state/queries/preferences/moderation' @@ -44,6 +44,13 @@ export function usePreferencesQuery() { return DEFAULT_LOGGED_OUT_PREFERENCES } else { const res = await agent.getPreferences() + + // save to local storage to ensure there are labels on initial requests + saveLabelers( + agent.session.did, + res.moderationPrefs.labelers.map(l => l.did), + ) + const preferences: UsePreferencesQueryResponse = { ...res, feeds: { @@ -54,32 +61,6 @@ export function usePreferencesQuery() { return !res.feeds.pinned?.includes(f) }) || [], }, - // labels are undefined until set by user - contentLabels: { - nsfw: temp__migrateLabelPref( - res.contentLabels?.nsfw || DEFAULT_LABEL_PREFERENCES.nsfw, - ), - nudity: temp__migrateLabelPref( - res.contentLabels?.nudity || DEFAULT_LABEL_PREFERENCES.nudity, - ), - suggestive: temp__migrateLabelPref( - res.contentLabels?.suggestive || - DEFAULT_LABEL_PREFERENCES.suggestive, - ), - gore: temp__migrateLabelPref( - res.contentLabels?.gore || DEFAULT_LABEL_PREFERENCES.gore, - ), - hate: temp__migrateLabelPref( - res.contentLabels?.hate || DEFAULT_LABEL_PREFERENCES.hate, - ), - spam: temp__migrateLabelPref( - res.contentLabels?.spam || DEFAULT_LABEL_PREFERENCES.spam, - ), - impersonation: temp__migrateLabelPref( - res.contentLabels?.impersonation || - DEFAULT_LABEL_PREFERENCES.impersonation, - ), - }, feedViewPrefs: { ...DEFAULT_HOME_FEED_PREFS, ...(res.feedViewPrefs.home || {}), @@ -96,25 +77,41 @@ export function usePreferencesQuery() { }) } +// used in the moderation state devtool +export const moderationOptsOverrideContext = createContext< + ModerationOpts | undefined +>(undefined) + export function useModerationOpts() { + const override = useContext(moderationOptsOverrideContext) const {currentAccount} = useSession() const prefs = usePreferencesQuery() - const hiddenPosts = useHiddenPosts() - const opts = useMemo(() => { + const {labelDefs} = useLabelDefinitions() + const hiddenPosts = useHiddenPosts() // TODO move this into pds-stored prefs + const opts = useMemo<ModerationOpts | undefined>(() => { + if (override) { + return override + } if (!prefs.data) { return } - const moderationOpts = getModerationOpts({ - userDid: currentAccount?.did || '', - preferences: prefs.data, - }) - return { - ...moderationOpts, - hiddenPosts, - mutedWords: prefs.data.mutedWords || [], + userDid: currentAccount?.did, + prefs: { + ...prefs.data.moderationPrefs, + labelers: prefs.data.moderationPrefs.labelers.length + ? prefs.data.moderationPrefs.labelers + : [ + { + did: BSKY_LABELER_DID, + labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES, + }, + ], + hiddenPosts: hiddenPosts || [], + }, + labelDefs, } - }, [currentAccount?.did, prefs.data, hiddenPosts]) + }, [override, currentAccount, labelDefs, prefs.data, hiddenPosts]) return opts } @@ -138,10 +135,32 @@ export function usePreferencesSetContentLabelMutation() { return useMutation< void, unknown, - {labelGroup: ConfigurableLabelGroup; visibility: LabelPreference} + {label: string; visibility: LabelPreference; labelerDid: string | undefined} >({ - mutationFn: async ({labelGroup, visibility}) => { - await getAgent().setContentLabelPref(labelGroup, visibility) + mutationFn: async ({label, visibility, labelerDid}) => { + await getAgent().setContentLabelPref(label, visibility, labelerDid) + // triggers a refetch + await queryClient.invalidateQueries({ + queryKey: preferencesQueryKey, + }) + }, + }) +} + +export function useSetContentLabelMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + label, + visibility, + labelerDid, + }: { + label: string + visibility: LabelPreference + labelerDid?: string + }) => { + await getAgent().setContentLabelPref(label, visibility, labelerDid) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, diff --git a/src/state/queries/preferences/moderation.ts b/src/state/queries/preferences/moderation.ts index cdae52937..9cd183e8b 100644 --- a/src/state/queries/preferences/moderation.ts +++ b/src/state/queries/preferences/moderation.ts @@ -1,181 +1,53 @@ +import React from 'react' import { - LabelPreference, - ComAtprotoLabelDefs, - ModerationOpts, + DEFAULT_LABEL_SETTINGS, + BskyAgent, + interpretLabelValueDefinitions, } from '@atproto/api' -import { - LabelGroup, - ConfigurableLabelGroup, - UsePreferencesQueryResponse, -} from '#/state/queries/preferences/types' - -export type Label = ComAtprotoLabelDefs.Label - -export type LabelGroupConfig = { - id: LabelGroup - title: string - isAdultImagery?: boolean - subtitle?: string - warning: string - values: string[] -} - -export const DEFAULT_LABEL_PREFERENCES: Record< - ConfigurableLabelGroup, - LabelPreference -> = { - nsfw: 'hide', - nudity: 'warn', - suggestive: 'warn', - gore: 'warn', - hate: 'hide', - spam: 'hide', - impersonation: 'hide', -} +import {usePreferencesQuery} from './index' +import {useLabelersDetailedInfoQuery} from '../labeler' /** * More strict than our default settings for logged in users. - * - * TODO(pwi) */ -export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: Record< - ConfigurableLabelGroup, - LabelPreference -> = { - nsfw: 'hide', - nudity: 'hide', - suggestive: 'hide', - gore: 'hide', - hate: 'hide', - spam: 'hide', - impersonation: 'hide', -} - -export const ILLEGAL_LABEL_GROUP: LabelGroupConfig = { - id: 'illegal', - title: 'Illegal Content', - warning: 'Illegal Content', - values: ['csam', 'dmca-violation', 'nudity-nonconsensual'], -} - -export const ALWAYS_FILTER_LABEL_GROUP: LabelGroupConfig = { - id: 'always-filter', - title: 'Content Warning', - warning: 'Content Warning', - values: ['!filter'], -} - -export const ALWAYS_WARN_LABEL_GROUP: LabelGroupConfig = { - id: 'always-warn', - title: 'Content Warning', - warning: 'Content Warning', - values: ['!warn', 'account-security'], -} - -export const UNKNOWN_LABEL_GROUP: LabelGroupConfig = { - id: 'unknown', - title: 'Unknown Label', - warning: 'Content Warning', - values: [], -} - -export const CONFIGURABLE_LABEL_GROUPS: Record< - ConfigurableLabelGroup, - LabelGroupConfig -> = { - nsfw: { - id: 'nsfw', - title: 'Explicit Sexual Images', - subtitle: 'i.e. pornography', - warning: 'Sexually Explicit', - values: ['porn', 'nsfl'], - isAdultImagery: true, - }, - nudity: { - id: 'nudity', - title: 'Other Nudity', - subtitle: 'Including non-sexual and artistic', - warning: 'Nudity', - values: ['nudity'], - isAdultImagery: true, - }, - suggestive: { - id: 'suggestive', - title: 'Sexually Suggestive', - subtitle: 'Does not include nudity', - warning: 'Sexually Suggestive', - values: ['sexual'], - isAdultImagery: true, - }, - gore: { - id: 'gore', - title: 'Violent / Bloody', - subtitle: 'Gore, self-harm, torture', - warning: 'Violence', - values: ['gore', 'self-harm', 'torture', 'nsfl', 'corpse'], - isAdultImagery: true, - }, - hate: { - id: 'hate', - title: 'Hate Group Iconography', - subtitle: 'Images of terror groups, articles covering events, etc.', - warning: 'Hate Groups', - values: ['icon-kkk', 'icon-nazi', 'icon-intolerant', 'behavior-intolerant'], - }, - spam: { - id: 'spam', - title: 'Spam', - subtitle: 'Excessive unwanted interactions', - warning: 'Spam', - values: ['spam'], - }, - impersonation: { - id: 'impersonation', - title: 'Impersonation', - subtitle: 'Accounts falsely claiming to be people or orgs', - warning: 'Impersonation', - values: ['impersonation'], - }, -} - -export function getModerationOpts({ - userDid, - preferences, -}: { - userDid: string - preferences: UsePreferencesQueryResponse -}): ModerationOpts { - return { - userDid: userDid, - adultContentEnabled: preferences.adultContentEnabled, - labels: { - porn: preferences.contentLabels.nsfw, - sexual: preferences.contentLabels.suggestive, - nudity: preferences.contentLabels.nudity, - nsfl: preferences.contentLabels.gore, - corpse: preferences.contentLabels.gore, - gore: preferences.contentLabels.gore, - torture: preferences.contentLabels.gore, - 'self-harm': preferences.contentLabels.gore, - 'intolerant-race': preferences.contentLabels.hate, - 'intolerant-gender': preferences.contentLabels.hate, - 'intolerant-sexual-orientation': preferences.contentLabels.hate, - 'intolerant-religion': preferences.contentLabels.hate, - intolerant: preferences.contentLabels.hate, - 'icon-intolerant': preferences.contentLabels.hate, - spam: preferences.contentLabels.spam, - impersonation: preferences.contentLabels.impersonation, - scam: 'warn', - }, - labelers: [ - { - labeler: { - did: '', - displayName: 'Bluesky Social', - }, - labels: {}, - }, - ], - } +export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: typeof DEFAULT_LABEL_SETTINGS = + Object.fromEntries( + Object.entries(DEFAULT_LABEL_SETTINGS).map(([key, _pref]) => [key, 'hide']), + ) + +export function useMyLabelersQuery() { + const prefs = usePreferencesQuery() + const dids = Array.from( + new Set( + BskyAgent.appLabelers.concat( + prefs.data?.moderationPrefs.labelers.map(l => l.did) || [], + ), + ), + ) + const labelers = useLabelersDetailedInfoQuery({dids}) + const isLoading = prefs.isLoading || labelers.isLoading + const error = prefs.error || labelers.error + return React.useMemo(() => { + return { + isLoading, + error, + data: labelers.data, + } + }, [labelers, isLoading, error]) +} + +export function useLabelDefinitionsQuery() { + const labelers = useMyLabelersQuery() + return React.useMemo(() => { + return { + labelDefs: Object.fromEntries( + (labelers.data || []).map(labeler => [ + labeler.creator.did, + interpretLabelValueDefinitions(labeler), + ]), + ), + labelers: labelers.data || [], + } + }, [labelers]) } diff --git a/src/state/queries/preferences/types.ts b/src/state/queries/preferences/types.ts index 45c9eed7d..96da16f1a 100644 --- a/src/state/queries/preferences/types.ts +++ b/src/state/queries/preferences/types.ts @@ -1,46 +1,13 @@ import { BskyPreferences, - LabelPreference, BskyThreadViewPreference, BskyFeedViewPreference, } from '@atproto/api' -export const configurableAdultLabelGroups = [ - 'nsfw', - 'nudity', - 'suggestive', - 'gore', -] as const - -export const configurableOtherLabelGroups = [ - 'hate', - 'spam', - 'impersonation', -] as const - -export const configurableLabelGroups = [ - ...configurableAdultLabelGroups, - ...configurableOtherLabelGroups, -] as const -export type ConfigurableLabelGroup = (typeof configurableLabelGroups)[number] - -export type LabelGroup = - | ConfigurableLabelGroup - | 'illegal' - | 'always-filter' - | 'always-warn' - | 'unknown' - export type UsePreferencesQueryResponse = Omit< BskyPreferences, 'contentLabels' | 'feedViewPrefs' | 'feeds' > & { - /* - * Content labels previously included 'show', which has been deprecated in - * favor of 'ignore'. The API can return legacy data from the database, and - * we clean up the data in `usePreferencesQuery`. - */ - contentLabels: Record<ConfigurableLabelGroup, LabelPreference> feedViewPrefs: BskyFeedViewPreference & { lab_mergeFeedEnabled?: boolean } diff --git a/src/state/queries/preferences/util.ts b/src/state/queries/preferences/util.ts deleted file mode 100644 index 7b8160c28..000000000 --- a/src/state/queries/preferences/util.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {LabelPreference} from '@atproto/api' - -/** - * Content labels previously included 'show', which has been deprecated in - * favor of 'ignore'. The API can return legacy data from the database, and - * we clean up the data in `usePreferencesQuery`. - * - * @deprecated - */ -export function temp__migrateLabelPref( - pref: LabelPreference | 'show', -): LabelPreference { - // @ts-ignore - if (pref === 'show') return 'ignore' - return pref -} diff --git a/src/state/queries/profile-extra-info.ts b/src/state/queries/profile-extra-info.ts deleted file mode 100644 index 8fc32c33e..000000000 --- a/src/state/queries/profile-extra-info.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {useQuery} from '@tanstack/react-query' - -import {getAgent} from '#/state/session' -import {STALE} from '#/state/queries' - -// TODO refactor invalidate on mutate? -export const RQKEY = (did: string) => ['profile-extra-info', did] - -/** - * Fetches some additional information for the profile screen which - * is not available in the API's ProfileView - */ -export function useProfileExtraInfoQuery(did: string) { - return useQuery({ - staleTime: STALE.MINUTES.ONE, - queryKey: RQKEY(did), - async queryFn() { - const [listsRes, feedsRes] = await Promise.all([ - getAgent().app.bsky.graph.getLists({ - actor: did, - limit: 1, - }), - getAgent().app.bsky.feed.getActorFeeds({ - actor: did, - limit: 1, - }), - ]) - return { - hasLists: listsRes.data.lists.length > 0, - hasFeedgens: feedsRes.data.feeds.length > 0, - } - }, - }) -} diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts index 932226b75..45b3ebb62 100644 --- a/src/state/queries/suggested-follows.ts +++ b/src/state/queries/suggested-follows.ts @@ -46,7 +46,8 @@ export function useSuggestedFollowsQuery() { res.data.actors = res.data.actors .filter( - actor => !moderateProfile(actor, moderationOpts!).account.filter, + actor => + !moderateProfile(actor, moderationOpts!).ui('profileList').filter, ) .filter(actor => { const viewer = actor.viewer diff --git a/src/state/session/agent-config.ts b/src/state/session/agent-config.ts new file mode 100644 index 000000000..3ee2718a3 --- /dev/null +++ b/src/state/session/agent-config.ts @@ -0,0 +1,12 @@ +import AsyncStorage from '@react-native-async-storage/async-storage' + +const PREFIX = 'agent-labelers' + +export async function saveLabelers(did: string, value: string[]) { + await AsyncStorage.setItem(`${PREFIX}:${did}`, JSON.stringify(value)) +} + +export async function readLabelers(did: string): Promise<string[] | undefined> { + const rawData = await AsyncStorage.getItem(`${PREFIX}:${did}`) + return rawData ? JSON.parse(rawData) : undefined +} diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index 46628318c..6b1474839 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -1,8 +1,15 @@ import React from 'react' -import {BskyAgent, AtpPersistSessionHandler} from '@atproto/api' +import { + BskyAgent, + AtpPersistSessionHandler, + BSKY_LABELER_DID, +} from '@atproto/api' import {useQueryClient} from '@tanstack/react-query' import {jwtDecode} from 'jwt-decode' +import {IS_DEV} from '#/env' +import {IS_TEST_USER} from '#/lib/constants' +import {isWeb} from '#/platform/detection' import {networkRetry} from '#/lib/async/retry' import {logger} from '#/logger' import * as persisted from '#/state/persisted' @@ -12,6 +19,7 @@ import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' import {track} from '#/lib/analytics/analytics' import {hasProp} from '#/lib/type-guards' +import {readLabelers} from './agent-config' let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT @@ -255,6 +263,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) { deactivated, } + await configureModeration(agent, account) + agent.setPersistSessionHandler( createPersistSessionHandler( account, @@ -298,6 +308,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) { deactivated: isSessionDeactivated(agent.session.accessJwt), } + await configureModeration(agent, account) + agent.setPersistSessionHandler( createPersistSessionHandler( account, @@ -309,6 +321,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) { ) __globalAgent = agent + // @ts-ignore + if (IS_DEV && isWeb) window.agent = agent queryClient.clear() upsertAccount(account) @@ -348,6 +362,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { {networkErrorCallback: clearCurrentAccount}, ), }) + // @ts-ignore + if (IS_DEV && isWeb) window.agent = agent + await configureModeration(agent, account) let canReusePrevSession = false try { @@ -643,6 +660,28 @@ export function Provider({children}: React.PropsWithChildren<{}>) { ) } +async function configureModeration(agent: BskyAgent, account: SessionAccount) { + if (IS_TEST_USER(account.handle)) { + const did = ( + await agent + .resolveHandle({handle: 'mod-authority.test'}) + .catch(_ => undefined) + )?.data.did + if (did) { + console.warn('USING TEST ENV MODERATION') + BskyAgent.configure({appLabelers: [did]}) + } + } else { + BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]}) + const labelerDids = await readLabelers(account.did).catch(_ => {}) + if (labelerDids) { + agent.configureLabelersHeader( + labelerDids.filter(did => did !== BSKY_LABELER_DID), + ) + } + } +} + export function useSession() { return React.useContext(StateContext) } diff --git a/src/state/shell/composer.tsx b/src/state/shell/composer.tsx index c9dbfbeac..5b4e50543 100644 --- a/src/state/shell/composer.tsx +++ b/src/state/shell/composer.tsx @@ -2,7 +2,8 @@ import React from 'react' import { AppBskyEmbedRecord, AppBskyRichtextFacet, - PostModeration, + ModerationDecision, + AppBskyActorDefs, } from '@atproto/api' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' @@ -10,13 +11,9 @@ export interface ComposerOptsPostRef { uri: string cid: string text: string - author: { - handle: string - displayName?: string - avatar?: string - } + author: AppBskyActorDefs.ProfileViewBasic embed?: AppBskyEmbedRecord.ViewRecord['embed'] - moderation?: PostModeration + moderation?: ModerationDecision } export interface ComposerOptsQuote { uri: string diff --git a/src/view/com/auth/HomeLoggedOutCTA.tsx b/src/view/com/auth/HomeLoggedOutCTA.tsx index f796d8bae..a5b5bf7ba 100644 --- a/src/view/com/auth/HomeLoggedOutCTA.tsx +++ b/src/view/com/auth/HomeLoggedOutCTA.tsx @@ -52,7 +52,9 @@ export function HomeLoggedOutCTA() { onPress={showCreateAccount} accessibilityRole="button" accessibilityLabel={_(msg`Create new account`)} - accessibilityHint="Opens flow to create a new Bluesky account"> + accessibilityHint={_( + msg`Opens flow to create a new Bluesky account`, + )}> <Text style={[ s.white, @@ -68,7 +70,9 @@ export function HomeLoggedOutCTA() { onPress={showSignIn} accessibilityRole="button" accessibilityLabel={_(msg`Sign in`)} - accessibilityHint="Opens flow to sign into your existing Bluesky account"> + accessibilityHint={_( + msg`Opens flow to sign into your existing Bluesky account`, + )}> <Text style={[ pal.text, diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx index 134ae11f1..f3d783476 100644 --- a/src/view/com/auth/SplashScreen.tsx +++ b/src/view/com/auth/SplashScreen.tsx @@ -66,7 +66,9 @@ export const SplashScreen = ({ onPress={onPressCreateAccount} accessibilityRole="button" accessibilityLabel={_(msg`Create new account`)} - accessibilityHint="Opens flow to create a new Bluesky account"> + accessibilityHint={_( + msg`Opens flow to create a new Bluesky account`, + )}> <Text style={[s.white, styles.btnLabel]}> <Trans>Create a new account</Trans> </Text> @@ -77,7 +79,9 @@ export const SplashScreen = ({ onPress={onPressSignin} accessibilityRole="button" accessibilityLabel={_(msg`Sign in`)} - accessibilityHint="Opens flow to sign into your existing Bluesky account"> + accessibilityHint={_( + msg`Opens flow to sign into your existing Bluesky account`, + )}> <Text style={[pal.text, styles.btnLabel]}> <Trans>Sign In</Trans> </Text> diff --git a/src/view/com/auth/create/Policies.tsx b/src/view/com/auth/create/Policies.tsx index 2c7d60818..803e2ad32 100644 --- a/src/view/com/auth/create/Policies.tsx +++ b/src/view/com/auth/create/Policies.tsx @@ -9,6 +9,8 @@ import {TextLink} from '../../util/Link' import {Text} from '../../util/text/Text' import {s, colors} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema @@ -20,6 +22,7 @@ export const Policies = ({ needsGuardian: boolean }) => { const pal = usePalette('default') + const {_} = useLingui() if (!serviceDescription) { return <View /> } @@ -40,7 +43,9 @@ export const Policies = ({ /> </View> <Text style={[pal.textLight, s.pl5, s.flex1]}> - This service has not provided terms of service or a privacy policy. + <Trans> + This service has not provided terms of service or a privacy policy. + </Trans> </Text> </View> ) @@ -51,7 +56,7 @@ export const Policies = ({ <TextLink key="tos" href={tos} - text="Terms of Service" + text={_(msg`Terms of Service`)} style={[pal.link, s.underline]} />, ) @@ -61,7 +66,7 @@ export const Policies = ({ <TextLink key="pp" href={pp} - text="Privacy Policy" + text={_(msg`Privacy Policy`)} style={[pal.link, s.underline]} />, ) @@ -79,12 +84,14 @@ export const Policies = ({ return ( <View style={styles.policies}> <Text style={pal.textLight}> - By creating an account you agree to the {els}. + <Trans>By creating an account you agree to the {els}.</Trans> </Text> {needsGuardian && ( <Text style={[pal.textLight, s.bold]}> - 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. + <Trans> + 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. + </Trans> </Text> )} </View> diff --git a/src/view/com/auth/create/state.ts b/src/view/com/auth/create/state.ts index 7a727ec0b..840084dcb 100644 --- a/src/view/com/auth/create/state.ts +++ b/src/view/com/auth/create/state.ts @@ -12,7 +12,7 @@ import {createFullHandle, validateHandle} from '#/lib/strings/handles' import {cleanError} from '#/lib/strings/errors' import {useOnboardingDispatch} from '#/state/shell/onboarding' import {useSessionApi} from '#/state/session' -import {DEFAULT_SERVICE, IS_PROD_SERVICE} from '#/lib/constants' +import {DEFAULT_SERVICE, IS_TEST_USER} from '#/lib/constants' import { DEFAULT_PROD_FEEDS, usePreferencesSetBirthDateMutation, @@ -147,7 +147,7 @@ export function useSubmitCreateAccount( : undefined, }) setBirthDate({birthDate: uiState.birthDate}) - if (IS_PROD_SERVICE(uiState.serviceUrl)) { + if (!IS_TEST_USER(uiState.handle)) { setSavedFeeds(DEFAULT_PROD_FEEDS) } } catch (e: any) { diff --git a/src/view/com/auth/login/ChooseAccountForm.tsx b/src/view/com/auth/login/ChooseAccountForm.tsx index 32cd8315d..d3b075fdb 100644 --- a/src/view/com/auth/login/ChooseAccountForm.tsx +++ b/src/view/com/auth/login/ChooseAccountForm.tsx @@ -45,7 +45,11 @@ function AccountItem({ accessibilityHint={_(msg`Double tap to sign in`)}> <View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> <View style={s.p10}> - <UserAvatar avatar={profile?.avatar} size={30} /> + <UserAvatar + avatar={profile?.avatar} + size={30} + type={profile?.associated?.labeler ? 'labeler' : 'user'} + /> </View> <Text style={styles.accountText}> <Text type="lg-bold" style={pal.text}> diff --git a/src/view/com/auth/login/LoginForm.tsx b/src/view/com/auth/login/LoginForm.tsx index fdba9f203..3202d69c5 100644 --- a/src/view/com/auth/login/LoginForm.tsx +++ b/src/view/com/auth/login/LoginForm.tsx @@ -207,7 +207,7 @@ export const LoginForm = ({ testID="loginPasswordInput" ref={passwordInputRef} style={[pal.text, styles.textInput]} - placeholder="Password" + placeholder={_(msg`Password`)} placeholderTextColor={pal.colors.textLight} autoCapitalize="none" autoCorrect={false} diff --git a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx index 5f81a4d65..dba3f8c56 100644 --- a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx +++ b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx @@ -1,6 +1,6 @@ import React from 'react' import {View, StyleSheet, ActivityIndicator} from 'react-native' -import {ProfileModeration, AppBskyActorDefs} from '@atproto/api' +import {ModerationDecision, AppBskyActorDefs} from '@atproto/api' import {Button} from '#/view/com/util/forms/Button' import {usePalette} from 'lib/hooks/usePalette' import {sanitizeDisplayName} from 'lib/strings/display-names' @@ -11,14 +11,15 @@ import {Text} from 'view/com/util/text/Text' import Animated, {FadeInRight} from 'react-native-reanimated' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useAnalytics} from 'lib/analytics/analytics' -import {Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {Trans, msg} from '@lingui/macro' import {Shadow, useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileFollowMutationQueue} from '#/state/queries/profile' import {logger} from '#/logger' type Props = { profile: AppBskyActorDefs.ProfileViewBasic - moderation: ProfileModeration + moderation: ModerationDecision onFollowStateChange: (props: { did: string following: boolean @@ -62,7 +63,7 @@ function ProfileCard({ moderation, }: { profile: Shadow<AppBskyActorDefs.ProfileViewBasic> - moderation: ProfileModeration + moderation: ModerationDecision onFollowStateChange: (props: { did: string following: boolean @@ -70,6 +71,7 @@ function ProfileCard({ }) { const {track} = useAnalytics() const pal = usePalette('default') + const {_} = useLingui() const [addingMoreSuggestions, setAddingMoreSuggestions] = React.useState(false) const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( @@ -113,7 +115,7 @@ function ProfileCard({ <UserAvatar size={40} avatar={profile.avatar} - moderation={moderation.avatar} + moderation={moderation.ui('avatar')} /> </View> <View style={styles.layoutContent}> @@ -124,7 +126,7 @@ function ProfileCard({ lineHeight={1.2}> {sanitizeDisplayName( profile.displayName || sanitizeHandle(profile.handle), - moderation.profile, + moderation.ui('displayName'), )} </Text> <Text type="xl" style={[pal.textLight]} numberOfLines={1}> @@ -136,7 +138,7 @@ function ProfileCard({ type={profile.viewer?.following ? 'default' : 'inverted'} labelStyle={styles.followButton} onPress={onToggleFollow} - label={profile.viewer?.following ? 'Unfollow' : 'Follow'} + label={profile.viewer?.following ? _(msg`Unfollow`) : _(msg`Follow`)} /> </View> {profile.description ? ( diff --git a/src/view/com/auth/onboarding/WelcomeMobile.tsx b/src/view/com/auth/onboarding/WelcomeMobile.tsx index 5de1a7817..b8659d56c 100644 --- a/src/view/com/auth/onboarding/WelcomeMobile.tsx +++ b/src/view/com/auth/onboarding/WelcomeMobile.tsx @@ -6,7 +6,8 @@ import {usePalette} from 'lib/hooks/usePalette' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Button} from 'view/com/util/forms/Button' import {ViewHeader} from 'view/com/util/ViewHeader' -import {Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {Trans, msg} from '@lingui/macro' type Props = { next: () => void @@ -15,6 +16,7 @@ type Props = { export function WelcomeMobile({next, skip}: Props) { const pal = usePalette('default') + const {_} = useLingui() return ( <View style={[styles.container]} testID="welcomeOnboarding"> @@ -91,7 +93,7 @@ export function WelcomeMobile({next, skip}: Props) { <Button onPress={next} - label="Continue" + label={_(msg`Continue`)} testID="continueBtn" style={[styles.buttonContainer]} labelStyle={styles.buttonText} diff --git a/src/view/com/auth/server-input/index.tsx b/src/view/com/auth/server-input/index.tsx index 32b5a3141..b26ac1dcb 100644 --- a/src/view/com/auth/server-input/index.tsx +++ b/src/view/com/auth/server-input/index.tsx @@ -115,7 +115,7 @@ export function ServerInputDialog({ testID="customServerTextInput" value={customAddress} onChangeText={setCustomAddress} - label={_(msg`my-server.com`)} + label="my-server.com" accessibilityLabelledBy="address-input-label" autoCapitalize="none" keyboardType="url" diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 97f8e5194..ddb01a8fa 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -39,7 +39,7 @@ import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useExternalLinkFetch} from './useExternalLinkFetch' import {isWeb, isNative, isAndroid, isIOS} from 'platform/detection' -import QuoteEmbed from '../util/post-embeds/QuoteEmbed' +import {QuoteEmbed} from '../util/post-embeds/QuoteEmbed' import {GalleryModel} from 'state/models/media/gallery' import {Gallery} from './photos/Gallery' import {MAX_GRAPHEME_LENGTH} from 'lib/constants' @@ -415,7 +415,11 @@ export const ComposePost = observer(function ComposePost({ styles.textInputLayout, isNative && styles.textInputLayoutMobile, ]}> - <UserAvatar avatar={currentProfile?.avatar} size={50} /> + <UserAvatar + avatar={currentProfile?.avatar} + size={50} + type={currentProfile?.associated?.labeler ? 'labeler' : 'user'} + /> <TextInput ref={textInput} richtext={richtext} @@ -443,7 +447,7 @@ export const ComposePost = observer(function ComposePost({ /> )} {quote ? ( - <View style={[s.mt5, isWeb && s.mb10]}> + <View style={[s.mt5, isWeb && s.mb10, {pointerEvents: 'none'}]}> <QuoteEmbed quote={quote} /> </View> ) : undefined} diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx index 39a1473a3..0c1b87d04 100644 --- a/src/view/com/composer/ComposerReplyTo.tsx +++ b/src/view/com/composer/ComposerReplyTo.tsx @@ -15,7 +15,7 @@ import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {UserAvatar} from 'view/com/util/UserAvatar' import {Text} from 'view/com/util/text/Text' -import QuoteEmbed from 'view/com/util/post-embeds/QuoteEmbed' +import {QuoteEmbed} from 'view/com/util/post-embeds/QuoteEmbed' export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { const pal = usePalette('default') @@ -86,7 +86,8 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { <UserAvatar avatar={replyTo.author.avatar} size={50} - moderation={replyTo.moderation?.avatar} + moderation={replyTo.moderation?.ui('avatar')} + type={replyTo.author.associated?.labeler ? 'labeler' : 'user'} /> <View style={styles.replyToPost}> <Text type="xl-medium" style={[pal.text]}> @@ -103,7 +104,7 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { {replyTo.text} </Text> </View> - {images && !replyTo.moderation?.embed.blur && ( + {images && !replyTo.moderation?.ui('contentMedia').blur && ( <ComposerReplyToImages images={images} showFull={showFull} /> )} </View> diff --git a/src/view/com/composer/Prompt.tsx b/src/view/com/composer/Prompt.tsx index 632bb2634..16d1b6fb9 100644 --- a/src/view/com/composer/Prompt.tsx +++ b/src/view/com/composer/Prompt.tsx @@ -23,7 +23,11 @@ export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) { accessibilityRole="button" accessibilityLabel={_(msg`Compose reply`)} accessibilityHint={_(msg`Opens composer`)}> - <UserAvatar avatar={profile?.avatar} size={38} /> + <UserAvatar + avatar={profile?.avatar} + size={38} + type={profile?.associated?.labeler ? 'labeler' : 'user'} + /> <Text type="xl" style={[ diff --git a/src/view/com/composer/select-language/SelectLangBtn.tsx b/src/view/com/composer/select-language/SelectLangBtn.tsx index 78b1e9ba2..785622225 100644 --- a/src/view/com/composer/select-language/SelectLangBtn.tsx +++ b/src/view/com/composer/select-language/SelectLangBtn.tsx @@ -20,7 +20,7 @@ import { toPostLanguages, hasPostLanguage, } from '#/state/preferences/languages' -import {t, msg} from '@lingui/macro' +import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' export function SelectLangBtn() { @@ -84,15 +84,15 @@ export function SelectLangBtn() { } return [ - {heading: true, label: t`Post language`}, + {heading: true, label: _(msg`Post language`)}, ...arr.slice(0, 6), {sep: true}, { - label: t`Other...`, + label: _(msg`Other...`), onPress: onPressMore, }, ] - }, [onPressMore, langPrefs, setLangPrefs, postLanguagesPref]) + }, [onPressMore, langPrefs, setLangPrefs, postLanguagesPref, _]) return ( <DropdownButton diff --git a/src/view/com/composer/text-input/mobile/Autocomplete.tsx b/src/view/com/composer/text-input/mobile/Autocomplete.tsx index c400aa48d..9c8f8f916 100644 --- a/src/view/com/composer/text-input/mobile/Autocomplete.tsx +++ b/src/view/com/composer/text-input/mobile/Autocomplete.tsx @@ -78,7 +78,11 @@ export function Autocomplete({ accessibilityLabel={`Select ${item.handle}`} accessibilityHint=""> <View style={styles.avatarAndHandle}> - <UserAvatar avatar={item.avatar ?? null} size={24} /> + <UserAvatar + avatar={item.avatar ?? null} + size={24} + type={item.associated?.labeler ? 'labeler' : 'user'} + /> <Text type="md-medium" style={pal.text}> {displayName} </Text> diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx index 76058fed3..29b8f0bc6 100644 --- a/src/view/com/composer/text-input/web/Autocomplete.tsx +++ b/src/view/com/composer/text-input/web/Autocomplete.tsx @@ -175,7 +175,11 @@ const MentionList = forwardRef<MentionListRef, SuggestionProps>( }} accessibilityRole="button"> <View style={styles.avatarAndDisplayName}> - <UserAvatar avatar={item.avatar ?? null} size={26} /> + <UserAvatar + avatar={item.avatar ?? null} + size={26} + type={item.associated?.labeler ? 'labeler' : 'user'} + /> <Text style={pal.text} numberOfLines={1}> {displayName} </Text> diff --git a/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx b/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx index b23dfedc8..3a040b5d0 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx @@ -6,16 +6,17 @@ * */ import React from 'react' -import {createHitslop} from 'lib/constants' import { SafeAreaView, TouchableOpacity, StyleSheet, ViewStyle, } from 'react-native' -import {t} from '@lingui/macro' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {usePalette} from '#/lib/hooks/usePalette' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {createHitslop} from '#/lib/constants' type Props = { onRequestClose: () => void @@ -24,8 +25,7 @@ type Props = { const HIT_SLOP = createHitslop(16) const ImageDefaultHeader = ({onRequestClose}: Props) => { - const pal = usePalette('default') - + const {_} = useLingui() return ( <SafeAreaView style={styles.root}> <TouchableOpacity @@ -33,10 +33,10 @@ const ImageDefaultHeader = ({onRequestClose}: Props) => { onPress={onRequestClose} hitSlop={HIT_SLOP} accessibilityRole="button" - accessibilityLabel={t`Close image`} - accessibilityHint={t`Closes viewer for header image`} + accessibilityLabel={_(msg`Close image`)} + accessibilityHint={_(msg`Closes viewer for header image`)} onAccessibilityEscape={onRequestClose}> - <FontAwesomeIcon icon="close" color={pal.colors.text} size={22} /> + <FontAwesomeIcon icon="close" color={'#fff'} size={22} /> </TouchableOpacity> </SafeAreaView> ) diff --git a/src/view/com/lightbox/Lightbox.tsx b/src/view/com/lightbox/Lightbox.tsx index 2ee5b8d59..5bab643ca 100644 --- a/src/view/com/lightbox/Lightbox.tsx +++ b/src/view/com/lightbox/Lightbox.tsx @@ -78,9 +78,9 @@ function LightboxFooter({imageIndex}: {imageIndex: number}) { try { await saveImageToMediaLibrary({uri}) - Toast.show('Saved to your camera roll.') + Toast.show(_(msg`Saved to your camera roll.`)) } catch (e: any) { - Toast.show(`Failed to save image: ${String(e)}`) + Toast.show(_(msg`Failed to save image: ${String(e)}`)) } }, [permissionResponse, requestPermission, _], diff --git a/src/view/com/modals/AppealLabel.tsx b/src/view/com/modals/AppealLabel.tsx deleted file mode 100644 index b0aaaf625..000000000 --- a/src/view/com/modals/AppealLabel.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React, {useState} from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {ComAtprotoModerationDefs} from '@atproto/api' -import {ScrollView, TextInput} from './util' -import {Text} from '../util/text/Text' -import {s, colors} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' -import {CharProgress} from '../composer/char-progress/CharProgress' -import {getAgent} from '#/state/session' -import * as Toast from '../util/Toast' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' - -export const snapPoints = ['40%'] - -type ReportComponentProps = - | { - uri: string - cid: string - } - | { - did: string - } - -export function Component(props: ReportComponentProps) { - const pal = usePalette('default') - const [details, setDetails] = useState<string>('') - const {_} = useLingui() - const {closeModal} = useModalControls() - const {isMobile} = useWebMediaQueries() - const isAccountReport = 'did' in props - - const submit = async () => { - try { - const $type = !isAccountReport - ? 'com.atproto.repo.strongRef' - : 'com.atproto.admin.defs#repoRef' - await getAgent().createModerationReport({ - reasonType: ComAtprotoModerationDefs.REASONAPPEAL, - subject: { - $type, - ...props, - }, - reason: details, - }) - Toast.show(_(msg`We'll look into your appeal promptly.`)) - } finally { - closeModal() - } - } - - return ( - <View - style={[ - pal.view, - s.flex1, - isMobile ? {paddingHorizontal: 12} : undefined, - ]} - testID="appealLabelModal"> - <Text - type="2xl-bold" - style={[pal.text, s.textCenter, {paddingBottom: 8}]}> - <Trans>Appeal Content Warning</Trans> - </Text> - <ScrollView> - <View style={[pal.btn, styles.detailsInputContainer]}> - <TextInput - accessibilityLabel={_(msg`Text input field`)} - accessibilityHint={_( - msg`Please tell us why you think this content warning was incorrectly applied!`, - )} - placeholder={_( - msg`Please tell us why you think this content warning was incorrectly applied!`, - )} - placeholderTextColor={pal.textLight.color} - value={details} - onChangeText={setDetails} - autoFocus={true} - numberOfLines={3} - multiline={true} - textAlignVertical="top" - maxLength={300} - style={[styles.detailsInput, pal.text]} - /> - <View style={styles.detailsInputBottomBar}> - <View style={styles.charCounter}> - <CharProgress count={details?.length || 0} /> - </View> - </View> - </View> - <TouchableOpacity - testID="confirmBtn" - onPress={submit} - style={styles.btn} - accessibilityRole="button" - accessibilityLabel={_(msg`Confirm`)} - accessibilityHint=""> - <Text style={[s.white, s.bold, s.f18]}> - <Trans>Submit</Trans> - </Text> - </TouchableOpacity> - </ScrollView> - </View> - ) -} - -const styles = StyleSheet.create({ - detailsInputContainer: { - borderRadius: 8, - marginBottom: 8, - }, - detailsInput: { - paddingHorizontal: 12, - paddingTop: 12, - paddingBottom: 12, - borderRadius: 8, - minHeight: 100, - fontSize: 16, - }, - detailsInputBottomBar: { - alignSelf: 'flex-end', - }, - charCounter: { - flexDirection: 'row', - alignItems: 'center', - paddingRight: 10, - paddingBottom: 8, - }, - btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - borderRadius: 32, - padding: 14, - backgroundColor: colors.blue3, - }, -}) diff --git a/src/view/com/modals/ChangeHandle.tsx b/src/view/com/modals/ChangeHandle.tsx index a43c30c29..f04bdb0e4 100644 --- a/src/view/com/modals/ChangeHandle.tsx +++ b/src/view/com/modals/ChangeHandle.tsx @@ -150,7 +150,7 @@ export function Inner({ accessibilityHint={_(msg`Exits handle change process`)} onAccessibilityEscape={onPressCancel}> <Text type="lg" style={pal.textLight}> - Cancel + <Trans>Cancel</Trans> </Text> </TouchableOpacity> </View> @@ -254,7 +254,7 @@ function ProvidedHandleForm({ <TextInput testID="setHandleInput" style={[pal.text, styles.textInput]} - placeholder="e.g. alice" + placeholder={_(msg`e.g. alice`)} placeholderTextColor={pal.colors.textLight} autoCapitalize="none" keyboardAppearance={theme.colorScheme} @@ -277,8 +277,8 @@ function ProvidedHandleForm({ <TouchableOpacity onPress={onToggleCustom} accessibilityRole="button" - accessibilityHint="Hosting provider" - accessibilityLabel={_(msg`Opens modal for using custom domain`)}> + accessibilityLabel={_(msg`Hosting provider`)} + accessibilityHint={_(msg`Opens modal for using custom domain`)}> <Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}> <Trans>I have my own domain</Trans> </Text> @@ -324,8 +324,8 @@ function CustomHandleForm({ Clipboard.setString( isDNSForm ? `did=${currentAccount.did}` : currentAccount.did, ) - Toast.show('Copied to clipboard') - }, [currentAccount, isDNSForm]) + Toast.show(_(msg`Copied to clipboard`)) + }, [currentAccount, isDNSForm, _]) const onChangeHandle = React.useCallback( (v: string) => { setHandle(v) @@ -378,7 +378,7 @@ function CustomHandleForm({ <TextInput testID="setHandleInput" style={[pal.text, styles.textInput]} - placeholder="e.g. alice.com" + placeholder={_(msg`e.g. alice.com`)} placeholderTextColor={pal.colors.textLight} autoCapitalize="none" keyboardAppearance={theme.colorScheme} @@ -387,7 +387,7 @@ function CustomHandleForm({ editable={!isProcessing} accessibilityLabelledBy="customDomain" accessibilityLabel={_(msg`Custom domain`)} - accessibilityHint="Input your preferred hosting provider" + accessibilityHint={_(msg`Input your preferred hosting provider`)} /> </View> <View style={styles.spacer} /> @@ -395,18 +395,18 @@ function CustomHandleForm({ <View style={[styles.selectableBtns]}> <SelectableBtn selected={isDNSForm} - label="DNS Panel" + label={_(msg`DNS Panel`)} left onSelect={() => setDNSForm(true)} - accessibilityHint="Use the DNS panel" + accessibilityHint={_(msg`Use the DNS panel`)} style={s.flex1} /> <SelectableBtn selected={!isDNSForm} - label="No DNS Panel" + label={_(msg`No DNS Panel`)} right onSelect={() => setDNSForm(false)} - accessibilityHint="Use a file on your server" + accessibilityHint={_(msg`Use a file on your server`)} style={s.flex1} /> </View> @@ -418,7 +418,7 @@ function CustomHandleForm({ </Text> <View style={[styles.dnsTable, pal.btn]}> <Text type="md-medium" style={[styles.dnsLabel, pal.text]}> - Host: + <Trans>Host:</Trans> </Text> <View style={[styles.dnsValue]}> <Text type="mono" style={[styles.monoText, pal.text]}> @@ -426,7 +426,7 @@ function CustomHandleForm({ </Text> </View> <Text type="md-medium" style={[styles.dnsLabel, pal.text]}> - Type: + <Trans>Type:</Trans> </Text> <View style={[styles.dnsValue]}> <Text type="mono" style={[styles.monoText, pal.text]}> @@ -434,7 +434,7 @@ function CustomHandleForm({ </Text> </View> <Text type="md-medium" style={[styles.dnsLabel, pal.text]}> - Value: + <Trans>Value:</Trans> </Text> <View style={[styles.dnsValue]}> <Text type="mono" style={[styles.monoText, pal.text]}> @@ -443,7 +443,7 @@ function CustomHandleForm({ </View> </View> <Text type="md" style={[pal.text, s.pt20, s.pl5]}> - This should create a domain record at:{' '} + <Trans>This should create a domain record at:</Trans> </Text> <Text type="mono" style={[styles.monoText, pal.text, s.pt5, s.pl5]}> _atproto.{handle} @@ -463,7 +463,7 @@ function CustomHandleForm({ </View> <View style={styles.spacer} /> <Text type="md" style={[pal.text, s.pb5, s.pl5]}> - That contains the following: + <Trans>That contains the following:</Trans> </Text> <View style={[styles.valueContainer, pal.btn]}> <View style={[styles.dnsValue]}> @@ -478,7 +478,9 @@ function CustomHandleForm({ <View style={styles.spacer} /> <Button type="default" style={[s.p20, s.mb10]} onPress={onPressCopy}> <Text type="xl" style={[pal.link, s.textCenter]}> - Copy {isDNSForm ? 'Domain Value' : 'File Contents'} + <Trans> + Copy {isDNSForm ? _(msg`Domain Value`) : _(msg`File Contents`)} + </Trans> </Text> </Button> {canSave === true && ( @@ -504,8 +506,8 @@ function CustomHandleForm({ ) : ( <Text type="xl-medium" style={[s.white, s.textCenter]}> {canSave - ? `Update to ${handle}` - : `Verify ${isDNSForm ? 'DNS Record' : 'Text File'}`} + ? _(msg`Update to ${handle}`) + : _(msg`Verify ${isDNSForm ? 'DNS Record' : 'Text File'}`)} </Text> )} </Button> @@ -513,9 +515,9 @@ function CustomHandleForm({ <TouchableOpacity onPress={onToggleCustom} accessibilityLabel={_(msg`Use default provider`)} - accessibilityHint="Use bsky.social as hosting provider"> + accessibilityHint={_(msg`Use bsky.social as hosting provider`)}> <Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}> - Nevermind, create a handle for me + <Trans>Nevermind, create a handle for me</Trans> </Text> </TouchableOpacity> </> diff --git a/src/view/com/modals/ChangePassword.tsx b/src/view/com/modals/ChangePassword.tsx index d8add9794..4badc88aa 100644 --- a/src/view/com/modals/ChangePassword.tsx +++ b/src/view/com/modals/ChangePassword.tsx @@ -137,7 +137,9 @@ export function Component() { <View> <View style={styles.titleSection}> <Text type="title-lg" style={[pal.text, styles.title]}> - {stage !== Stages.Done ? 'Change Password' : 'Password Changed'} + {stage !== Stages.Done + ? _(msg`Change Password`) + : _(msg`Password Changed`)} </Text> </View> @@ -180,7 +182,7 @@ export function Component() { <TextInput testID="codeInput" style={[pal.text, styles.textInput]} - placeholder="Reset code" + placeholder={_(msg`Reset code`)} placeholderTextColor={pal.colors.textLight} value={resetCode} onChangeText={setResetCode} @@ -207,7 +209,7 @@ export function Component() { <TextInput testID="codeInput" style={[pal.text, styles.textInput]} - placeholder="New password" + placeholder={_(msg`New password`)} placeholderTextColor={pal.colors.textLight} onChangeText={setNewPassword} secureTextEntry diff --git a/src/view/com/modals/ContentFilteringSettings.tsx b/src/view/com/modals/ContentFilteringSettings.tsx deleted file mode 100644 index 3c7edcf0d..000000000 --- a/src/view/com/modals/ContentFilteringSettings.tsx +++ /dev/null @@ -1,407 +0,0 @@ -import React from 'react' -import {LabelPreference} from '@atproto/api' -import {StyleSheet, Pressable, View, Linking} from 'react-native' -import LinearGradient from 'react-native-linear-gradient' -import {ScrollView} from './util' -import {s, colors, gradients} from 'lib/styles' -import {Text} from '../util/text/Text' -import {TextLink} from '../util/Link' -import {ToggleButton} from '../util/forms/ToggleButton' -import {Button} from '../util/forms/Button' -import {usePalette} from 'lib/hooks/usePalette' -import {isIOS} from 'platform/detection' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import * as Toast from '../util/Toast' -import {logger} from '#/logger' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' -import { - usePreferencesQuery, - usePreferencesSetContentLabelMutation, - usePreferencesSetAdultContentMutation, - ConfigurableLabelGroup, - CONFIGURABLE_LABEL_GROUPS, - UsePreferencesQueryResponse, -} from '#/state/queries/preferences' -import {useDialogControl} from '#/components/Dialog' -import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' - -export const snapPoints = ['90%'] - -export function Component({}: {}) { - const {isMobile} = useWebMediaQueries() - const pal = usePalette('default') - const {_} = useLingui() - const {closeModal} = useModalControls() - const {data: preferences} = usePreferencesQuery() - - const onPressDone = React.useCallback(() => { - closeModal() - }, [closeModal]) - - return ( - <View testID="contentFilteringModal" style={[pal.view, styles.container]}> - <Text style={[pal.text, styles.title]}> - <Trans>Content Filtering</Trans> - </Text> - - <ScrollView style={styles.scrollContainer}> - <AdultContentEnabledPref /> - <ContentLabelPref - preferences={preferences} - labelGroup="nsfw" - disabled={!preferences?.adultContentEnabled} - /> - <ContentLabelPref - preferences={preferences} - labelGroup="nudity" - disabled={!preferences?.adultContentEnabled} - /> - <ContentLabelPref - preferences={preferences} - labelGroup="suggestive" - disabled={!preferences?.adultContentEnabled} - /> - <ContentLabelPref - preferences={preferences} - labelGroup="gore" - disabled={!preferences?.adultContentEnabled} - /> - <ContentLabelPref preferences={preferences} labelGroup="hate" /> - <ContentLabelPref preferences={preferences} labelGroup="spam" /> - <ContentLabelPref - preferences={preferences} - labelGroup="impersonation" - /> - <View style={{height: isMobile ? 60 : 0}} /> - </ScrollView> - - <View - style={[ - styles.btnContainer, - isMobile && styles.btnContainerMobile, - pal.borderDark, - ]}> - <Pressable - testID="sendReportBtn" - onPress={onPressDone} - accessibilityRole="button" - accessibilityLabel={_(msg`Done`)} - accessibilityHint=""> - <LinearGradient - colors={[gradients.blueLight.start, gradients.blueLight.end]} - start={{x: 0, y: 0}} - end={{x: 1, y: 1}} - style={[styles.btn]}> - <Text style={[s.white, s.bold, s.f18]}> - <Trans>Done</Trans> - </Text> - </LinearGradient> - </Pressable> - </View> - </View> - ) -} - -function AdultContentEnabledPref() { - const pal = usePalette('default') - const {_} = useLingui() - const {data: preferences} = usePreferencesQuery() - const {mutate, variables} = usePreferencesSetAdultContentMutation() - const bithdayDialogControl = useDialogControl() - - const onSetAge = React.useCallback( - () => bithdayDialogControl.open(), - [bithdayDialogControl], - ) - - const onToggleAdultContent = React.useCallback(async () => { - if (isIOS) return - - try { - mutate({ - enabled: !(variables?.enabled ?? preferences?.adultContentEnabled), - }) - } catch (e) { - Toast.show( - _(msg`There was an issue syncing your preferences with the server`), - ) - logger.error('Failed to update preferences with server', {message: e}) - } - }, [variables, preferences, mutate, _]) - - const onAdultContentLinkPress = React.useCallback(() => { - Linking.openURL('https://bsky.app/') - }, []) - - return ( - <View style={s.mb10}> - <BirthDateSettingsDialog - control={bithdayDialogControl} - preferences={preferences} - /> - {isIOS ? ( - preferences?.adultContentEnabled ? null : ( - <Text type="md" style={pal.textLight}> - <Trans> - Adult content can only be enabled via the Web at{' '} - <TextLink - style={pal.link} - href="" - text="bsky.app" - onPress={onAdultContentLinkPress} - /> - . - </Trans> - </Text> - ) - ) : typeof preferences?.birthDate === 'undefined' ? ( - <View style={[pal.viewLight, styles.agePrompt]}> - <Text type="md" style={[pal.text, {flex: 1}]}> - <Trans>Confirm your age to enable adult content.</Trans> - </Text> - <Button - type="primary" - label={_(msg({message: 'Set Age', context: 'action'}))} - onPress={onSetAge} - /> - </View> - ) : (preferences.userAge || 0) >= 18 ? ( - <ToggleButton - type="default-light" - label={_(msg`Enable Adult Content`)} - isSelected={variables?.enabled ?? preferences?.adultContentEnabled} - onPress={onToggleAdultContent} - style={styles.toggleBtn} - /> - ) : ( - <View style={[pal.viewLight, styles.agePrompt]}> - <Text type="md" style={[pal.text, {flex: 1}]}> - <Trans>You must be 18 or older to enable adult content.</Trans> - </Text> - <Button - type="primary" - label={_(msg({message: 'Set Age', context: 'action'}))} - onPress={onSetAge} - /> - </View> - )} - </View> - ) -} - -// TODO: Refactor this component to pass labels down to each tab -function ContentLabelPref({ - preferences, - labelGroup, - disabled, -}: { - preferences?: UsePreferencesQueryResponse - labelGroup: ConfigurableLabelGroup - disabled?: boolean -}) { - const pal = usePalette('default') - const visibility = preferences?.contentLabels?.[labelGroup] - const {mutate, variables} = usePreferencesSetContentLabelMutation() - - const onChange = React.useCallback( - (vis: LabelPreference) => { - mutate({labelGroup, visibility: vis}) - }, - [mutate, labelGroup], - ) - - return ( - <View style={[styles.contentLabelPref, pal.border]}> - <View style={s.flex1}> - <Text type="md-medium" style={[pal.text]}> - {CONFIGURABLE_LABEL_GROUPS[labelGroup].title} - </Text> - {typeof CONFIGURABLE_LABEL_GROUPS[labelGroup].subtitle === 'string' && ( - <Text type="sm" style={[pal.textLight]}> - {CONFIGURABLE_LABEL_GROUPS[labelGroup].subtitle} - </Text> - )} - </View> - - {disabled || !visibility ? ( - <Text type="sm-bold" style={pal.textLight}> - <Trans context="action">Hide</Trans> - </Text> - ) : ( - <SelectGroup - current={variables?.visibility || visibility} - onChange={onChange} - labelGroup={labelGroup} - /> - )} - </View> - ) -} - -interface SelectGroupProps { - current: LabelPreference - onChange: (v: LabelPreference) => void - labelGroup: ConfigurableLabelGroup -} - -function SelectGroup({current, onChange, labelGroup}: SelectGroupProps) { - const {_} = useLingui() - - return ( - <View style={styles.selectableBtns}> - <SelectableBtn - current={current} - value="hide" - label={_(msg`Hide`)} - left - onChange={onChange} - labelGroup={labelGroup} - /> - <SelectableBtn - current={current} - value="warn" - label={_(msg`Warn`)} - onChange={onChange} - labelGroup={labelGroup} - /> - <SelectableBtn - current={current} - value="ignore" - label={_(msg`Show`)} - right - onChange={onChange} - labelGroup={labelGroup} - /> - </View> - ) -} - -interface SelectableBtnProps { - current: string - value: LabelPreference - label: string - left?: boolean - right?: boolean - onChange: (v: LabelPreference) => void - labelGroup: ConfigurableLabelGroup -} - -function SelectableBtn({ - current, - value, - label, - left, - right, - onChange, - labelGroup, -}: SelectableBtnProps) { - const pal = usePalette('default') - const palPrimary = usePalette('inverted') - const {_} = useLingui() - - return ( - <Pressable - style={[ - styles.selectableBtn, - left && styles.selectableBtnLeft, - right && styles.selectableBtnRight, - pal.border, - current === value ? palPrimary.view : pal.view, - ]} - onPress={() => onChange(value)} - accessibilityRole="button" - accessibilityLabel={value} - accessibilityHint={_( - msg`Set ${value} for ${labelGroup} content moderation policy`, - )}> - <Text style={current === value ? palPrimary.text : pal.text}> - {label} - </Text> - </Pressable> - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - title: { - textAlign: 'center', - fontWeight: 'bold', - fontSize: 24, - marginBottom: 12, - }, - description: { - paddingHorizontal: 2, - marginBottom: 10, - }, - scrollContainer: { - flex: 1, - paddingHorizontal: 10, - }, - btnContainer: { - paddingTop: 10, - paddingHorizontal: 10, - }, - btnContainerMobile: { - paddingBottom: 40, - borderTopWidth: 1, - }, - - agePrompt: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingLeft: 14, - paddingRight: 10, - paddingVertical: 8, - borderRadius: 8, - }, - - contentLabelPref: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingTop: 14, - paddingLeft: 4, - marginBottom: 14, - borderTopWidth: 1, - }, - - selectableBtns: { - flexDirection: 'row', - marginLeft: 10, - }, - selectableBtn: { - flexDirection: 'row', - justifyContent: 'center', - borderWidth: 1, - borderLeftWidth: 0, - paddingHorizontal: 10, - paddingVertical: 10, - }, - selectableBtnLeft: { - borderTopLeftRadius: 8, - borderBottomLeftRadius: 8, - borderLeftWidth: 1, - }, - selectableBtnRight: { - borderTopRightRadius: 8, - borderBottomRightRadius: 8, - }, - - btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - borderRadius: 32, - padding: 14, - backgroundColor: colors.gray1, - }, - toggleBtn: { - paddingHorizontal: 0, - }, -}) diff --git a/src/view/com/modals/DeleteAccount.tsx b/src/view/com/modals/DeleteAccount.tsx index 40d78cfe0..2301e7a66 100644 --- a/src/view/com/modals/DeleteAccount.tsx +++ b/src/view/com/modals/DeleteAccount.tsx @@ -173,7 +173,7 @@ export function Component({}: {}) { </Text> <TextInput style={[styles.textInput, pal.borderDark, pal.text, styles.mb20]} - placeholder="Confirmation code" + placeholder={_(msg`Confirmation code`)} placeholderTextColor={pal.textLight.color} keyboardAppearance={theme.colorScheme} value={confirmCode} @@ -192,7 +192,7 @@ export function Component({}: {}) { </Text> <TextInput style={[styles.textInput, pal.borderDark, pal.text]} - placeholder="Password" + placeholder={_(msg`Password`)} placeholderTextColor={pal.textLight.color} keyboardAppearance={theme.colorScheme} secureTextEntry @@ -228,7 +228,7 @@ export function Component({}: {}) { onPress={onCancel} accessibilityRole="button" accessibilityLabel={_(msg`Cancel account deletion`)} - accessibilityHint="Exits account deletion process" + accessibilityHint={_(msg`Exits account deletion process`)} onAccessibilityEscape={onCancel}> <Text type="button-lg" style={pal.textLight}> <Trans context="action">Cancel</Trans> diff --git a/src/view/com/modals/InAppBrowserConsent.tsx b/src/view/com/modals/InAppBrowserConsent.tsx index 86bb46ca8..3fa515934 100644 --- a/src/view/com/modals/InAppBrowserConsent.tsx +++ b/src/view/com/modals/InAppBrowserConsent.tsx @@ -77,7 +77,7 @@ export function Component({href}: {href: string}) { }} accessibilityLabel={_(msg`Cancel`)} accessibilityHint="" - label="Cancel" + label={_(msg`Cancel`)} labelContainerStyle={{justifyContent: 'center', padding: 8}} labelStyle={[s.f18]} /> diff --git a/src/view/com/modals/LinkWarning.tsx b/src/view/com/modals/LinkWarning.tsx index 81fdc7285..b5ff6700d 100644 --- a/src/view/com/modals/LinkWarning.tsx +++ b/src/view/com/modals/LinkWarning.tsx @@ -73,8 +73,8 @@ export function Component({text, href}: {text: string; href: string}) { type="primary" onPress={onPressVisit} accessibilityLabel={_(msg`Visit Site`)} - accessibilityHint="" - label="Visit Site" + accessibilityHint={_(msg`Opens the linked website`)} + label={_(msg`Visit Site`)} labelContainerStyle={{justifyContent: 'center', padding: 4}} labelStyle={[s.f18]} /> @@ -85,8 +85,8 @@ export function Component({text, href}: {text: string; href: string}) { closeModal() }} accessibilityLabel={_(msg`Cancel`)} - accessibilityHint="" - label="Cancel" + accessibilityHint={_(msg`Cancels opening the linked website`)} + label={_(msg`Cancel`)} labelContainerStyle={{justifyContent: 'center', padding: 4}} labelStyle={[s.f18]} /> diff --git a/src/view/com/modals/ListAddRemoveUsers.tsx b/src/view/com/modals/ListAddRemoveUsers.tsx index 27c33f806..4715348dd 100644 --- a/src/view/com/modals/ListAddRemoveUsers.tsx +++ b/src/view/com/modals/ListAddRemoveUsers.tsx @@ -231,7 +231,11 @@ function UserResult({ width: 54, paddingLeft: 4, }}> - <UserAvatar size={40} avatar={profile.avatar} /> + <UserAvatar + size={40} + avatar={profile.avatar} + type={profile.associated?.labeler ? 'labeler' : 'user'} + /> </View> <View style={{ diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index e382e6fab..238cfc502 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -15,16 +15,12 @@ import * as UserAddRemoveListsModal from './UserAddRemoveLists' import * as ListAddUserModal from './ListAddRemoveUsers' import * as AltImageModal from './AltImage' import * as EditImageModal from './AltImage' -import * as ReportModal from './report/Modal' -import * as AppealLabelModal from './AppealLabel' import * as DeleteAccountModal from './DeleteAccount' import * as ChangeHandleModal from './ChangeHandle' import * as InviteCodesModal from './InviteCodes' import * as AddAppPassword from './AddAppPasswords' -import * as ContentFilteringSettingsModal from './ContentFilteringSettings' import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' -import * as ModerationDetailsModal from './ModerationDetails' import * as VerifyEmailModal from './VerifyEmail' import * as ChangeEmailModal from './ChangeEmail' import * as ChangePasswordModal from './ChangePassword' @@ -67,12 +63,6 @@ export function ModalsContainer() { if (activeModal?.name === 'edit-profile') { snapPoints = EditProfileModal.snapPoints element = <EditProfileModal.Component {...activeModal} /> - } else if (activeModal?.name === 'report') { - snapPoints = ReportModal.snapPoints - element = <ReportModal.Component {...activeModal} /> - } else if (activeModal?.name === 'appeal-label') { - snapPoints = AppealLabelModal.snapPoints - element = <AppealLabelModal.Component {...activeModal} /> } else if (activeModal?.name === 'create-or-edit-list') { snapPoints = CreateOrEditListModal.snapPoints element = <CreateOrEditListModal.Component {...activeModal} /> @@ -109,18 +99,12 @@ export function ModalsContainer() { } else if (activeModal?.name === 'add-app-password') { snapPoints = AddAppPassword.snapPoints element = <AddAppPassword.Component /> - } else if (activeModal?.name === 'content-filtering-settings') { - snapPoints = ContentFilteringSettingsModal.snapPoints - element = <ContentFilteringSettingsModal.Component /> } else if (activeModal?.name === 'content-languages-settings') { snapPoints = ContentLanguagesSettingsModal.snapPoints element = <ContentLanguagesSettingsModal.Component /> } else if (activeModal?.name === 'post-languages-settings') { snapPoints = PostLanguagesSettingsModal.snapPoints element = <PostLanguagesSettingsModal.Component /> - } else if (activeModal?.name === 'moderation-details') { - snapPoints = ModerationDetailsModal.snapPoints - element = <ModerationDetailsModal.Component {...activeModal} /> } else if (activeModal?.name === 'verify-email') { snapPoints = VerifyEmailModal.snapPoints element = <VerifyEmailModal.Component {...activeModal} /> diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 66ea2311f..7e5d548ac 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -8,8 +8,6 @@ import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' import {useModals, useModalControls} from '#/state/modals' import type {Modal as ModalIface} from '#/state/modals' import * as EditProfileModal from './EditProfile' -import * as ReportModal from './report/Modal' -import * as AppealLabelModal from './AppealLabel' import * as CreateOrEditListModal from './CreateOrEditList' import * as UserAddRemoveLists from './UserAddRemoveLists' import * as ListAddUserModal from './ListAddRemoveUsers' @@ -23,10 +21,8 @@ import * as EditImageModal from './EditImage' import * as ChangeHandleModal from './ChangeHandle' import * as InviteCodesModal from './InviteCodes' import * as AddAppPassword from './AddAppPasswords' -import * as ContentFilteringSettingsModal from './ContentFilteringSettings' import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' -import * as ModerationDetailsModal from './ModerationDetails' import * as VerifyEmailModal from './VerifyEmail' import * as ChangeEmailModal from './ChangeEmail' import * as ChangePasswordModal from './ChangePassword' @@ -78,10 +74,6 @@ function Modal({modal}: {modal: ModalIface}) { let element if (modal.name === 'edit-profile') { element = <EditProfileModal.Component {...modal} /> - } else if (modal.name === 'report') { - element = <ReportModal.Component {...modal} /> - } else if (modal.name === 'appeal-label') { - element = <AppealLabelModal.Component {...modal} /> } else if (modal.name === 'create-or-edit-list') { element = <CreateOrEditListModal.Component {...modal} /> } else if (modal.name === 'user-add-remove-lists') { @@ -104,8 +96,6 @@ function Modal({modal}: {modal: ModalIface}) { element = <InviteCodesModal.Component /> } else if (modal.name === 'add-app-password') { element = <AddAppPassword.Component /> - } else if (modal.name === 'content-filtering-settings') { - element = <ContentFilteringSettingsModal.Component /> } else if (modal.name === 'content-languages-settings') { element = <ContentLanguagesSettingsModal.Component /> } else if (modal.name === 'post-languages-settings') { @@ -114,8 +104,6 @@ function Modal({modal}: {modal: ModalIface}) { element = <AltTextImageModal.Component {...modal} /> } else if (modal.name === 'edit-image') { element = <EditImageModal.Component {...modal} /> - } else if (modal.name === 'moderation-details') { - element = <ModerationDetailsModal.Component {...modal} /> } else if (modal.name === 'verify-email') { element = <VerifyEmailModal.Component {...modal} /> } else if (modal.name === 'change-email') { diff --git a/src/view/com/modals/ModerationDetails.tsx b/src/view/com/modals/ModerationDetails.tsx deleted file mode 100644 index f890d50dc..000000000 --- a/src/view/com/modals/ModerationDetails.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import React from 'react' -import {StyleSheet, View} from 'react-native' -import {ModerationUI} from '@atproto/api' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {s} from 'lib/styles' -import {Text} from '../util/text/Text' -import {TextLink} from '../util/Link' -import {usePalette} from 'lib/hooks/usePalette' -import {isWeb} from 'platform/detection' -import {listUriToHref} from 'lib/strings/url-helpers' -import {Button} from '../util/forms/Button' -import {useModalControls} from '#/state/modals' -import {useLingui} from '@lingui/react' -import {Trans, msg} from '@lingui/macro' - -export const snapPoints = [300] - -export function Component({ - context, - moderation, -}: { - context: 'account' | 'content' - moderation: ModerationUI -}) { - const {closeModal} = useModalControls() - const {isMobile} = useWebMediaQueries() - const pal = usePalette('default') - const {_} = useLingui() - - let name - let description - if (!moderation.cause) { - name = _(msg`Content Warning`) - description = _( - msg`Moderator has chosen to set a general warning on the content.`, - ) - } else if (moderation.cause.type === 'blocking') { - if (moderation.cause.source.type === 'list') { - const list = moderation.cause.source.list - name = _(msg`User Blocked by List`) - description = ( - <Trans> - This user is included in the{' '} - <TextLink - type="2xl" - href={listUriToHref(list.uri)} - text={list.name} - style={pal.link} - />{' '} - list which you have blocked. - </Trans> - ) - } else { - name = _(msg`User Blocked`) - description = _( - msg`You have blocked this user. You cannot view their content.`, - ) - } - } else if (moderation.cause.type === 'blocked-by') { - name = _(msg`User Blocks You`) - description = _( - msg`This user has blocked you. You cannot view their content.`, - ) - } else if (moderation.cause.type === 'block-other') { - name = _(msg`Content Not Available`) - description = _( - msg`This content is not available because one of the users involved has blocked the other.`, - ) - } else if (moderation.cause.type === 'muted') { - if (moderation.cause.source.type === 'list') { - const list = moderation.cause.source.list - name = _(msg`Account Muted by List`) - description = ( - <Trans> - This user is included in the{' '} - <TextLink - type="2xl" - href={listUriToHref(list.uri)} - text={list.name} - style={pal.link} - />{' '} - list which you have muted. - </Trans> - ) - } else { - name = _(msg`Account Muted`) - description = _(msg`You have muted this user.`) - } - } else { - name = moderation.cause.labelDef.strings[context].en.name - description = moderation.cause.labelDef.strings[context].en.description - } - - return ( - <View - testID="moderationDetailsModal" - style={[ - styles.container, - { - paddingHorizontal: isMobile ? 14 : 0, - }, - pal.view, - ]}> - <Text type="title-xl" style={[pal.text, styles.title]}> - {name} - </Text> - <Text type="2xl" style={[pal.text, styles.description]}> - {description} - </Text> - <View style={s.flex1} /> - <Button - type="primary" - style={styles.btn} - onPress={() => { - closeModal() - }}> - <Text type="button-lg" style={[pal.textLight, s.textCenter, s.white]}> - Okay - </Text> - </Button> - </View> - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - title: { - textAlign: 'center', - fontWeight: 'bold', - marginBottom: 12, - }, - description: { - textAlign: 'center', - }, - btn: { - paddingVertical: 14, - marginTop: isWeb ? 40 : 0, - marginBottom: isWeb ? 0 : 40, - }, -}) diff --git a/src/view/com/modals/SwitchAccount.tsx b/src/view/com/modals/SwitchAccount.tsx index c034c4b52..0658805bd 100644 --- a/src/view/com/modals/SwitchAccount.tsx +++ b/src/view/com/modals/SwitchAccount.tsx @@ -45,7 +45,11 @@ function SwitchAccountCard({account}: {account: SessionAccount}) { const contents = ( <View style={[pal.view, styles.linkCard]}> <View style={styles.avi}> - <UserAvatar size={40} avatar={profile?.avatar} /> + <UserAvatar + size={40} + avatar={profile?.avatar} + type={profile?.associated?.labeler ? 'labeler' : 'user'} + /> </View> <View style={[s.flex1]}> <Text type="md-bold" style={pal.text} numberOfLines={1}> diff --git a/src/view/com/modals/UserAddRemoveLists.tsx b/src/view/com/modals/UserAddRemoveLists.tsx index 8452f2513..8a61b1a70 100644 --- a/src/view/com/modals/UserAddRemoveLists.tsx +++ b/src/view/com/modals/UserAddRemoveLists.tsx @@ -180,7 +180,7 @@ function ListItem({ }, ]}> <View style={styles.listItemAvi}> - <UserAvatar size={40} avatar={list.avatar} /> + <UserAvatar size={40} avatar={list.avatar} type="list" /> </View> <View style={styles.listItemContent}> <Text diff --git a/src/view/com/modals/VerifyEmail.tsx b/src/view/com/modals/VerifyEmail.tsx index 30a57afc5..d3086d383 100644 --- a/src/view/com/modals/VerifyEmail.tsx +++ b/src/view/com/modals/VerifyEmail.tsx @@ -149,7 +149,7 @@ export function Component({showReminder}: {showReminder?: boolean}) { onPress={onEmailIncorrect} style={styles.changeEmailLink}> <Text type="lg" style={pal.link}> - Change + <Trans>Change</Trans> </Text> </Pressable> </> diff --git a/src/view/com/modals/crop-image/CropImage.web.tsx b/src/view/com/modals/crop-image/CropImage.web.tsx index 6f094a1fd..98a2494ed 100644 --- a/src/view/com/modals/crop-image/CropImage.web.tsx +++ b/src/view/com/modals/crop-image/CropImage.web.tsx @@ -100,7 +100,7 @@ export function Component({ onPress={doSetAs(AspectRatio.Wide)} accessibilityRole="button" accessibilityLabel={_(msg`Wide`)} - accessibilityHint="Sets image aspect ratio to wide"> + accessibilityHint={_(msg`Sets image aspect ratio to wide`)}> <RectWideIcon size={24} style={as === AspectRatio.Wide ? s.blue3 : pal.text} @@ -110,7 +110,7 @@ export function Component({ onPress={doSetAs(AspectRatio.Tall)} accessibilityRole="button" accessibilityLabel={_(msg`Tall`)} - accessibilityHint="Sets image aspect ratio to tall"> + accessibilityHint={_(msg`Sets image aspect ratio to tall`)}> <RectTallIcon size={24} style={as === AspectRatio.Tall ? s.blue3 : pal.text} @@ -120,7 +120,7 @@ export function Component({ onPress={doSetAs(AspectRatio.Square)} accessibilityRole="button" accessibilityLabel={_(msg`Square`)} - accessibilityHint="Sets image aspect ratio to square"> + accessibilityHint={_(msg`Sets image aspect ratio to square`)}> <SquareIcon size={24} style={as === AspectRatio.Square ? s.blue3 : pal.text} @@ -132,9 +132,9 @@ export function Component({ onPress={onPressCancel} accessibilityRole="button" accessibilityLabel={_(msg`Cancel image crop`)} - accessibilityHint="Exits image cropping process"> + accessibilityHint={_(msg`Exits image cropping process`)}> <Text type="xl" style={pal.link}> - Cancel + <Trans>Cancel</Trans> </Text> </TouchableOpacity> <View style={s.flex1} /> @@ -142,7 +142,7 @@ export function Component({ onPress={onPressDone} accessibilityRole="button" accessibilityLabel={_(msg`Save image crop`)} - accessibilityHint="Saves image crop settings"> + accessibilityHint={_(msg`Saves image crop settings`)}> <LinearGradient colors={[gradients.blueLight.start, gradients.blueLight.end]} start={{x: 0, y: 0}} diff --git a/src/view/com/modals/report/InputIssueDetails.tsx b/src/view/com/modals/report/InputIssueDetails.tsx deleted file mode 100644 index 2bc86f75e..000000000 --- a/src/view/com/modals/report/InputIssueDetails.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from 'react' -import {View, TouchableOpacity, StyleSheet} from 'react-native' -import {TextInput} from '../util' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {CharProgress} from '../../composer/char-progress/CharProgress' -import {Text} from '../../util/text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {s} from 'lib/styles' -import {SendReportButton} from './SendReportButton' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -export function InputIssueDetails({ - details, - setDetails, - goBack, - submitReport, - isProcessing, -}: { - details: string | undefined - setDetails: (v: string) => void - goBack: () => void - submitReport: () => void - isProcessing: boolean -}) { - const pal = usePalette('default') - const {_} = useLingui() - const {isMobile} = useWebMediaQueries() - - return ( - <View - style={{ - marginTop: isMobile ? 12 : 0, - }}> - <TouchableOpacity - testID="addDetailsBtn" - style={[s.mb10, styles.backBtn]} - onPress={goBack} - accessibilityRole="button" - accessibilityLabel={_(msg`Add details`)} - accessibilityHint="Add more details to your report"> - <FontAwesomeIcon size={18} icon="angle-left" style={[pal.link]} /> - <Text style={[pal.text, s.f18, pal.link]}> - {' '} - <Trans>Back</Trans> - </Text> - </TouchableOpacity> - <View style={[pal.btn, styles.detailsInputContainer]}> - <TextInput - accessibilityLabel={_(msg`Text input field`)} - accessibilityHint="Enter a reason for reporting this post." - placeholder="Enter a reason or any other details here." - placeholderTextColor={pal.textLight.color} - value={details} - onChangeText={setDetails} - autoFocus={true} - numberOfLines={3} - multiline={true} - textAlignVertical="top" - maxLength={300} - style={[styles.detailsInput, pal.text]} - /> - <View style={styles.detailsInputBottomBar}> - <View style={styles.charCounter}> - <CharProgress count={details?.length || 0} /> - </View> - </View> - </View> - <SendReportButton onPress={submitReport} isProcessing={isProcessing} /> - </View> - ) -} - -const styles = StyleSheet.create({ - backBtn: { - flexDirection: 'row', - alignItems: 'center', - }, - detailsInputContainer: { - borderRadius: 8, - }, - detailsInput: { - paddingHorizontal: 12, - paddingTop: 12, - paddingBottom: 12, - borderRadius: 8, - minHeight: 100, - fontSize: 16, - }, - detailsInputBottomBar: { - alignSelf: 'flex-end', - }, - charCounter: { - flexDirection: 'row', - alignItems: 'center', - paddingRight: 10, - paddingBottom: 8, - }, -}) diff --git a/src/view/com/modals/report/Modal.tsx b/src/view/com/modals/report/Modal.tsx deleted file mode 100644 index abbad9b40..000000000 --- a/src/view/com/modals/report/Modal.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import React, {useState, useMemo} from 'react' -import {Linking, StyleSheet, TouchableOpacity, View} from 'react-native' -import {ScrollView} from 'react-native-gesture-handler' -import {AtUri} from '@atproto/api' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {s} from 'lib/styles' -import {Text} from '../../util/text/Text' -import * as Toast from '../../util/Toast' -import {ErrorMessage} from '../../util/error/ErrorMessage' -import {cleanError} from 'lib/strings/errors' -import {usePalette} from 'lib/hooks/usePalette' -import {SendReportButton} from './SendReportButton' -import {InputIssueDetails} from './InputIssueDetails' -import {ReportReasonOptions} from './ReasonOptions' -import {CollectionId} from './types' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' -import {getAgent} from '#/state/session' - -const DMCA_LINK = 'https://bsky.social/about/support/copyright' - -export const snapPoints = [575] - -const CollectionNames = { - [CollectionId.FeedGenerator]: 'Feed', - [CollectionId.Profile]: 'Profile', - [CollectionId.List]: 'List', - [CollectionId.Post]: 'Post', -} - -type ReportComponentProps = - | { - uri: string - cid: string - } - | { - did: string - } - -export function Component(content: ReportComponentProps) { - const {closeModal} = useModalControls() - const pal = usePalette('default') - const {isMobile} = useWebMediaQueries() - const [isProcessing, setIsProcessing] = useState(false) - const [showDetailsInput, setShowDetailsInput] = useState(false) - const [error, setError] = useState<string>('') - const [issue, setIssue] = useState<string>('') - const [details, setDetails] = useState<string>('') - const isAccountReport = 'did' in content - const subjectKey = isAccountReport ? content.did : content.uri - const atUri = useMemo( - () => (!isAccountReport ? new AtUri(subjectKey) : null), - [isAccountReport, subjectKey], - ) - - const submitReport = async () => { - setError('') - if (!issue) { - return - } - setIsProcessing(true) - try { - if (issue === '__copyright__') { - Linking.openURL(DMCA_LINK) - closeModal() - return - } - const $type = !isAccountReport - ? 'com.atproto.repo.strongRef' - : 'com.atproto.admin.defs#repoRef' - await getAgent().createModerationReport({ - reasonType: issue, - subject: { - $type, - ...content, - }, - reason: details, - }) - Toast.show("Thank you for your report! We'll look into it promptly.") - - closeModal() - return - } catch (e: any) { - setError(cleanError(e)) - setIsProcessing(false) - } - } - - const goBack = () => { - setShowDetailsInput(false) - } - - return ( - <ScrollView testID="reportModal" style={[s.flex1, pal.view]}> - <View - style={[ - styles.container, - isMobile && { - paddingBottom: 40, - }, - ]}> - {showDetailsInput ? ( - <InputIssueDetails - details={details} - setDetails={setDetails} - goBack={goBack} - submitReport={submitReport} - isProcessing={isProcessing} - /> - ) : ( - <SelectIssue - setShowDetailsInput={setShowDetailsInput} - error={error} - issue={issue} - setIssue={setIssue} - submitReport={submitReport} - isProcessing={isProcessing} - atUri={atUri} - /> - )} - </View> - </ScrollView> - ) -} - -// If no atUri is passed, that means the reporting collection is account -const getCollectionNameForReport = (atUri: AtUri | null) => { - if (!atUri) return 'Account' - // Generic fallback for any collection being reported - return CollectionNames[atUri.collection as CollectionId] || 'Content' -} - -const SelectIssue = ({ - error, - setShowDetailsInput, - issue, - setIssue, - submitReport, - isProcessing, - atUri, -}: { - error: string | undefined - setShowDetailsInput: (v: boolean) => void - issue: string | undefined - setIssue: (v: string) => void - submitReport: () => void - isProcessing: boolean - atUri: AtUri | null -}) => { - const pal = usePalette('default') - const {_} = useLingui() - const collectionName = getCollectionNameForReport(atUri) - const onSelectIssue = (v: string) => setIssue(v) - const goToDetails = () => { - if (issue === '__copyright__') { - Linking.openURL(DMCA_LINK) - return - } - setShowDetailsInput(true) - } - - return ( - <> - <Text style={[pal.text, styles.title]}> - <Trans>Report {collectionName}</Trans> - </Text> - <Text style={[pal.textLight, styles.description]}> - <Trans>What is the issue with this {collectionName}?</Trans> - </Text> - <View style={{marginBottom: 10}}> - <ReportReasonOptions - atUri={atUri} - selectedIssue={issue} - onSelectIssue={onSelectIssue} - /> - </View> - {error ? <ErrorMessage message={error} /> : undefined} - {/* If no atUri is present, the report would be for account in which case, we allow sending without specifying a reason */} - {issue || !atUri ? ( - <> - <SendReportButton - onPress={submitReport} - isProcessing={isProcessing} - /> - <TouchableOpacity - testID="addDetailsBtn" - style={styles.addDetailsBtn} - onPress={goToDetails} - accessibilityRole="button" - accessibilityLabel={_(msg`Add details`)} - accessibilityHint="Add more details to your report"> - <Text style={[s.f18, pal.link]}> - <Trans>Add details to report</Trans> - </Text> - </TouchableOpacity> - </> - ) : undefined} - </> - ) -} - -const styles = StyleSheet.create({ - container: { - paddingHorizontal: 10, - }, - title: { - textAlign: 'center', - fontWeight: 'bold', - fontSize: 24, - marginBottom: 12, - }, - description: { - textAlign: 'center', - fontSize: 17, - paddingHorizontal: 22, - marginBottom: 10, - }, - addDetailsBtn: { - padding: 14, - alignSelf: 'center', - }, -}) diff --git a/src/view/com/modals/report/ReasonOptions.tsx b/src/view/com/modals/report/ReasonOptions.tsx deleted file mode 100644 index 23b49b664..000000000 --- a/src/view/com/modals/report/ReasonOptions.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import {View} from 'react-native' -import React, {useMemo} from 'react' -import {AtUri, ComAtprotoModerationDefs} from '@atproto/api' - -import {Text} from '../../util/text/Text' -import {UsePaletteValue, usePalette} from 'lib/hooks/usePalette' -import {RadioGroup, RadioGroupItem} from 'view/com/util/forms/RadioGroup' -import {CollectionId} from './types' - -type ReasonMap = Record<string, {title: string; description: string}> -const CommonReasons = { - [ComAtprotoModerationDefs.REASONRUDE]: { - title: 'Anti-Social Behavior', - description: 'Harassment, trolling, or intolerance', - }, - [ComAtprotoModerationDefs.REASONVIOLATION]: { - title: 'Illegal and Urgent', - description: 'Glaring violations of law or terms of service', - }, - [ComAtprotoModerationDefs.REASONOTHER]: { - title: 'Other', - description: 'An issue not included in these options', - }, -} -const CollectionToReasonsMap: Record<string, ReasonMap> = { - [CollectionId.Post]: { - [ComAtprotoModerationDefs.REASONSPAM]: { - title: 'Spam', - description: 'Excessive mentions or replies', - }, - [ComAtprotoModerationDefs.REASONSEXUAL]: { - title: 'Unwanted Sexual Content', - description: 'Nudity or pornography not labeled as such', - }, - __copyright__: { - title: 'Copyright Violation', - description: 'Contains copyrighted material', - }, - ...CommonReasons, - }, - [CollectionId.List]: { - ...CommonReasons, - [ComAtprotoModerationDefs.REASONVIOLATION]: { - title: 'Name or Description Violates Community Standards', - description: 'Terms used violate community standards', - }, - }, -} -const AccountReportReasons = { - [ComAtprotoModerationDefs.REASONMISLEADING]: { - title: 'Misleading Account', - description: 'Impersonation or false claims about identity or affiliation', - }, - [ComAtprotoModerationDefs.REASONSPAM]: { - title: 'Frequently Posts Unwanted Content', - description: 'Spam; excessive mentions or replies', - }, - [ComAtprotoModerationDefs.REASONVIOLATION]: { - title: 'Name or Description Violates Community Standards', - description: 'Terms used violate community standards', - }, -} - -const Option = ({ - pal, - title, - description, -}: { - pal: UsePaletteValue - description: string - title: string -}) => { - return ( - <View> - <Text style={pal.text} type="md-bold"> - {title} - </Text> - <Text style={pal.textLight}>{description}</Text> - </View> - ) -} - -// This is mostly just content copy without almost any logic -// so this may grow over time and it makes sense to split it up into its own file -// to keep it separate from the actual reporting modal logic -const useReportRadioOptions = (pal: UsePaletteValue, atUri: AtUri | null) => - useMemo(() => { - let items: ReasonMap = {...CommonReasons} - // If no atUri is passed, that means the reporting collection is account - if (!atUri) { - items = {...AccountReportReasons} - } - - if (atUri?.collection && CollectionToReasonsMap[atUri.collection]) { - items = {...CollectionToReasonsMap[atUri.collection]} - } - - return Object.entries(items).map(([key, {title, description}]) => ({ - key, - label: <Option pal={pal} title={title} description={description} />, - })) - }, [pal, atUri]) - -export const ReportReasonOptions = ({ - atUri, - selectedIssue, - onSelectIssue, -}: { - atUri: AtUri | null - selectedIssue?: string - onSelectIssue: (key: string) => void -}) => { - const pal = usePalette('default') - const ITEMS: RadioGroupItem[] = useReportRadioOptions(pal, atUri) - return ( - <RadioGroup - items={ITEMS} - onSelect={onSelectIssue} - testID="reportReasonRadios" - initialSelection={selectedIssue} - /> - ) -} diff --git a/src/view/com/modals/report/SendReportButton.tsx b/src/view/com/modals/report/SendReportButton.tsx deleted file mode 100644 index 40c239bff..000000000 --- a/src/view/com/modals/report/SendReportButton.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react' -import LinearGradient from 'react-native-linear-gradient' -import { - ActivityIndicator, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' -import {Text} from '../../util/text/Text' -import {s, gradients, colors} from 'lib/styles' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -export function SendReportButton({ - onPress, - isProcessing, -}: { - onPress: () => void - isProcessing: boolean -}) { - const {_} = useLingui() - // loading state - // = - if (isProcessing) { - return ( - <View style={[styles.btn, s.mt10]}> - <ActivityIndicator /> - </View> - ) - } - return ( - <TouchableOpacity - testID="sendReportBtn" - style={s.mt10} - onPress={onPress} - accessibilityRole="button" - accessibilityLabel={_(msg`Report post`)} - accessibilityHint={`Reports post with reason and details`}> - <LinearGradient - colors={[gradients.blueLight.start, gradients.blueLight.end]} - start={{x: 0, y: 0}} - end={{x: 1, y: 1}} - style={[styles.btn]}> - <Text style={[s.white, s.bold, s.f18]}> - <Trans>Send Report</Trans> - </Text> - </LinearGradient> - </TouchableOpacity> - ) -} - -const styles = StyleSheet.create({ - btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - borderRadius: 32, - padding: 14, - backgroundColor: colors.gray1, - }, -}) diff --git a/src/view/com/modals/report/types.ts b/src/view/com/modals/report/types.ts deleted file mode 100644 index ca947ecbd..000000000 --- a/src/view/com/modals/report/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -// TODO: ATM, @atproto/api does not export ids but it does have these listed at @atproto/api/client/lexicons -// once we start exporting the ids from the @atproto/ap package, replace these hardcoded ones -export enum CollectionId { - FeedGenerator = 'app.bsky.feed.generator', - Profile = 'app.bsky.actor.profile', - List = 'app.bsky.graph.list', - Post = 'app.bsky.feed.post', -} diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index a46870265..78b1677c3 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -11,9 +11,10 @@ import { AppBskyFeedDefs, AppBskyFeedPost, ModerationOpts, - ProfileModeration, + ModerationDecision, moderateProfile, AppBskyEmbedRecordWithMedia, + AppBskyActorDefs, } from '@atproto/api' import {AtUri} from '@atproto/api' import { @@ -54,7 +55,8 @@ interface Author { handle: string displayName?: string avatar?: string - moderation: ProfileModeration + moderation: ModerationDecision + associated?: AppBskyActorDefs.ProfileAssociated } let FeedItem = ({ @@ -100,6 +102,7 @@ let FeedItem = ({ displayName: item.notification.author.displayName, avatar: item.notification.author.avatar, moderation: moderateProfile(item.notification.author, moderationOpts), + associated: item.notification.author.associated, }, ...(item.additional?.map(({author}) => { return { @@ -109,6 +112,7 @@ let FeedItem = ({ displayName: author.displayName, avatar: author.avatar, moderation: moderateProfile(author, moderationOpts), + associated: author.associated, } }) || []), ] @@ -336,7 +340,8 @@ function CondensedAuthorsList({ did={authors[0].did} handle={authors[0].handle} avatar={authors[0].avatar} - moderation={authors[0].moderation.avatar} + moderation={authors[0].moderation.ui('avatar')} + type={authors[0].associated?.labeler ? 'labeler' : 'user'} /> </View> ) @@ -354,7 +359,8 @@ function CondensedAuthorsList({ <UserAvatar size={35} avatar={author.avatar} - moderation={author.moderation.avatar} + moderation={author.moderation.ui('avatar')} + type={author.associated?.labeler ? 'labeler' : 'user'} /> </View> ))} @@ -412,7 +418,8 @@ function ExpandedAuthorsList({ <UserAvatar size={35} avatar={author.avatar} - moderation={author.moderation.avatar} + moderation={author.moderation.ui('avatar')} + type={author.associated?.labeler ? 'labeler' : 'user'} /> </View> <View style={s.flex1}> diff --git a/src/view/com/post-thread/PostLikedBy.tsx b/src/view/com/post-thread/PostLikedBy.tsx index 55463dc13..0760ed7ff 100644 --- a/src/view/com/post-thread/PostLikedBy.tsx +++ b/src/view/com/post-thread/PostLikedBy.tsx @@ -8,7 +8,7 @@ import {ProfileCardWithFollowBtn} from '../profile/ProfileCard' import {logger} from '#/logger' import {LoadingScreen} from '../util/LoadingScreen' import {useResolveUriQuery} from '#/state/queries/resolve-uri' -import {usePostLikedByQuery} from '#/state/queries/post-liked-by' +import {useLikedByQuery} from '#/state/queries/post-liked-by' import {cleanError} from '#/lib/strings/errors' export function PostLikedBy({uri}: {uri: string}) { @@ -28,7 +28,7 @@ export function PostLikedBy({uri}: {uri: string}) { isError, error, refetch, - } = usePostLikedByQuery(resolvedUri?.uri) + } = useLikedByQuery(resolvedUri?.uri) const likes = useMemo(() => { if (data?.pages) { return data.pages.flatMap(page => page.likes) diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index a7ee42a94..8042e7bd5 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -1,25 +1,14 @@ import React, {useEffect, useRef} from 'react' -import { - ActivityIndicator, - Pressable, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' +import {StyleSheet, useWindowDimensions, View} from 'react-native' import {AppBskyFeedDefs} from '@atproto/api' -import {CenteredView} from '../util/Views' -import {LoadingScreen} from '../util/LoadingScreen' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + import {List, ListMethods} from '../util/List' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' import {PostThreadItem} from './PostThreadItem' import {ComposePrompt} from '../composer/Prompt' import {ViewHeader} from '../util/ViewHeader' -import {ErrorMessage} from '../util/error/ErrorMessage' import {Text} from '../util/text/Text' -import {s} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' import {useSetTitle} from 'lib/hooks/useSetTitle' import { @@ -30,21 +19,18 @@ import { usePostThreadQuery, sortThread, } from '#/state/queries/post-thread' -import {useNavigation} from '@react-navigation/native' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {NavigationProp} from 'lib/routes/types' import {sanitizeDisplayName} from 'lib/strings/display-names' -import {cleanError} from '#/lib/strings/errors' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' import { - UsePreferencesQueryResponse, useModerationOpts, usePreferencesQuery, } from '#/state/queries/preferences' import {useSession} from '#/state/session' import {isAndroid, isNative, isWeb} from '#/platform/detection' import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' +import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' +import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {cleanError} from 'lib/strings/errors' // FlatList maintainVisibleContentPosition breaks if too many items // are prepended. This seems to be an optimal number based on *shrug*. @@ -58,9 +44,7 @@ const MAINTAIN_VISIBLE_CONTENT_POSITION = { const TOP_COMPONENT = {_reactKey: '__top_component__'} const REPLY_PROMPT = {_reactKey: '__reply__'} -const CHILD_SPINNER = {_reactKey: '__child_spinner__'} const LOAD_MORE = {_reactKey: '__load_more__'} -const BOTTOM_COMPONENT = {_reactKey: '__bottom_component__'} type YieldedItem = ThreadPost | ThreadBlocked | ThreadNotFound type RowItem = @@ -68,9 +52,7 @@ type RowItem = // TODO: TS doesn't actually enforce it's one of these, it only enforces matching shape. | typeof TOP_COMPONENT | typeof REPLY_PROMPT - | typeof CHILD_SPINNER | typeof LOAD_MORE - | typeof BOTTOM_COMPONENT type ThreadSkeletonParts = { parents: YieldedItem[] @@ -78,6 +60,10 @@ type ThreadSkeletonParts = { replies: YieldedItem[] } +const keyExtractor = (item: RowItem) => { + return item._reactKey +} + export function PostThread({ uri, onCanReply, @@ -85,17 +71,30 @@ export function PostThread({ }: { uri: string | undefined onCanReply: (canReply: boolean) => void - onPressReply: () => void + onPressReply: () => unknown }) { + const {hasSession} = useSession() + const {_} = useLingui() + const pal = usePalette('default') + const {isMobile, isTabletOrMobile} = useWebMediaQueries() + const initialNumToRender = useInitialNumToRender() + const {height: windowHeight} = useWindowDimensions() + + const {data: preferences} = usePreferencesQuery() const { - isLoading, - isError, - error, + isFetching, + isError: isThreadError, + error: threadError, refetch, data: thread, } = usePostThreadQuery(uri) - const {data: preferences} = usePreferencesQuery() + const treeView = React.useMemo( + () => + !!preferences?.threadViewPrefs?.lab_treeViewEnabled && + hasBranchingReplies(thread), + [preferences?.threadViewPrefs, thread], + ) const rootPost = thread?.type === 'post' ? thread.post : undefined const rootPostRecord = thread?.type === 'post' ? thread.record : undefined @@ -105,14 +104,22 @@ export function PostThread({ rootPost && moderationOpts ? moderatePost(rootPost, moderationOpts) : undefined - - const cause = mod?.content.cause - - return cause - ? cause.type === 'label' && cause.labelDef.id === '!no-unauthenticated' - : false + return !!mod + ?.ui('contentList') + .blurs.find( + cause => + cause.type === 'label' && cause.labelDef.id === '!no-unauthenticated', + ) }, [rootPost, moderationOpts]) + // Values used for proper rendering of parents + const ref = useRef<ListMethods>(null) + const highlightedPostRef = useRef<View | null>(null) + const [maxParents, setMaxParents] = React.useState( + isWeb ? Infinity : PARENTS_CHUNK_SIZE, + ) + const [maxReplies, setMaxReplies] = React.useState(50) + useSetTitle( rootPost && !isNoPwi ? `${sanitizeDisplayName( @@ -120,62 +127,6 @@ export function PostThread({ )}: "${rootPostRecord!.text}"` : '', ) - useEffect(() => { - if (rootPost) { - onCanReply(!rootPost.viewer?.replyDisabled) - } - }, [rootPost, onCanReply]) - - if (isError || AppBskyFeedDefs.isNotFoundPost(thread)) { - return ( - <PostThreadError - error={error} - notFound={AppBskyFeedDefs.isNotFoundPost(thread)} - onRefresh={refetch} - /> - ) - } - if (AppBskyFeedDefs.isBlockedPost(thread)) { - return <PostThreadBlocked /> - } - if (!thread || isLoading || !preferences) { - return <LoadingScreen /> - } - return ( - <PostThreadLoaded - thread={thread} - threadViewPrefs={preferences.threadViewPrefs} - onRefresh={refetch} - onPressReply={onPressReply} - /> - ) -} - -function PostThreadLoaded({ - thread, - threadViewPrefs, - onRefresh, - onPressReply, -}: { - thread: ThreadNode - threadViewPrefs: UsePreferencesQueryResponse['threadViewPrefs'] - onRefresh: () => void - onPressReply: () => void -}) { - const {hasSession} = useSession() - const {_} = useLingui() - const pal = usePalette('default') - const {isMobile, isTabletOrMobile} = useWebMediaQueries() - const ref = useRef<ListMethods>(null) - const highlightedPostRef = useRef<View | null>(null) - const [maxParents, setMaxParents] = React.useState( - isWeb ? Infinity : PARENTS_CHUNK_SIZE, - ) - const [maxReplies, setMaxReplies] = React.useState(100) - const treeView = React.useMemo( - () => !!threadViewPrefs.lab_treeViewEnabled && hasBranchingReplies(thread), - [threadViewPrefs, thread], - ) // On native, this is going to start out `true`. We'll toggle it to `false` after the initial render if flushed. // This ensures that the first render contains no parents--even if they are already available in the cache. @@ -183,18 +134,56 @@ function PostThreadLoaded({ // On the web this is not necessary because we can synchronously adjust the scroll in onContentSizeChange instead. const [deferParents, setDeferParents] = React.useState(isNative) - const skeleton = React.useMemo( - () => - createThreadSkeleton( - sortThread(thread, threadViewPrefs), - hasSession, - treeView, - ), - [thread, threadViewPrefs, hasSession, treeView], - ) + const skeleton = React.useMemo(() => { + const threadViewPrefs = preferences?.threadViewPrefs + if (!threadViewPrefs || !thread) return null + + return createThreadSkeleton( + sortThread(thread, threadViewPrefs), + hasSession, + treeView, + ) + }, [thread, preferences?.threadViewPrefs, hasSession, treeView]) + + const error = React.useMemo(() => { + if (AppBskyFeedDefs.isNotFoundPost(thread)) { + return { + title: _(msg`Post not found`), + message: _(msg`The post may have been deleted.`), + } + } else if (skeleton?.highlightedPost.type === 'blocked') { + return { + title: _(msg`Post hidden`), + message: _( + msg`You have blocked the author or you have been blocked by the author.`, + ), + } + } else if (threadError?.message.startsWith('Post not found')) { + return { + title: _(msg`Post not found`), + message: _(msg`The post may have been deleted.`), + } + } else if (isThreadError) { + return { + message: threadError ? cleanError(threadError) : undefined, + } + } + + return null + }, [thread, skeleton?.highlightedPost, isThreadError, _, threadError]) + + useEffect(() => { + if (error) { + onCanReply(false) + } else if (rootPost) { + onCanReply(!rootPost.viewer?.replyDisabled) + } + }, [rootPost, onCanReply, error]) // construct content const posts = React.useMemo(() => { + if (!skeleton) return [] + const {parents, highlightedPost, replies} = skeleton let arr: RowItem[] = [] if (highlightedPost.type === 'post') { @@ -230,17 +219,11 @@ function PostThreadLoaded({ if (!highlightedPost.post.viewer?.replyDisabled) { arr.push(REPLY_PROMPT) } - if (highlightedPost.ctx.isChildLoading) { - arr.push(CHILD_SPINNER) - } else { - for (let i = 0; i < replies.length; i++) { - arr.push(replies[i]) - if (i === maxReplies) { - arr.push(LOAD_MORE) - break - } + for (let i = 0; i < replies.length; i++) { + arr.push(replies[i]) + if (i === maxReplies) { + break } - arr.push(BOTTOM_COMPONENT) } } return arr @@ -255,7 +238,7 @@ function PostThreadLoaded({ return } // wait for loading to finish - if (thread.type === 'post' && !!thread.parent) { + if (thread?.type === 'post' && !!thread.parent) { function onMeasure(pageY: number) { ref.current?.scrollToOffset({ animated: false, @@ -279,10 +262,10 @@ function PostThreadLoaded({ // To work around this, we prepend rows after scroll bumps against the top and rests. const needsBumpMaxParents = React.useRef(false) const onStartReached = React.useCallback(() => { - if (maxParents < skeleton.parents.length) { + if (skeleton?.parents && maxParents < skeleton.parents.length) { needsBumpMaxParents.current = true } - }, [maxParents, skeleton.parents.length]) + }, [maxParents, skeleton?.parents]) const bumpMaxParentsIfNeeded = React.useCallback(() => { if (!isNative) { return @@ -295,6 +278,11 @@ function PostThreadLoaded({ const onMomentumScrollEnd = bumpMaxParentsIfNeeded const onScrollToTop = bumpMaxParentsIfNeeded + const onEndReached = React.useCallback(() => { + if (isFetching || posts.length < maxReplies) return + setMaxReplies(prev => prev + 50) + }, [isFetching, maxReplies, posts.length]) + const renderItem = React.useCallback( ({item, index}: {item: RowItem; index: number}) => { if (item === TOP_COMPONENT) { @@ -325,46 +313,6 @@ function PostThreadLoaded({ </Text> </View> ) - } else if (item === LOAD_MORE) { - return ( - <Pressable - onPress={() => setMaxReplies(n => n + 50)} - style={[pal.border, pal.view, styles.itemContainer]} - accessibilityLabel={_(msg`Load more posts`)} - accessibilityHint=""> - <View - style={[ - pal.viewLight, - {paddingHorizontal: 18, paddingVertical: 14, borderRadius: 6}, - ]}> - <Text type="lg-medium" style={pal.text}> - <Trans>Load more posts</Trans> - </Text> - </View> - </Pressable> - ) - } else if (item === BOTTOM_COMPONENT) { - // HACK - // due to some complexities with how flatlist works, this is the easiest way - // I could find to get a border positioned directly under the last item - // -prf - return ( - <View - // @ts-ignore web-only - style={{ - // Leave enough space below that the scroll doesn't jump - height: isNative ? 600 : '100vh', - borderTopWidth: 1, - borderColor: pal.colors.border, - }} - /> - ) - } else if (item === CHILD_SPINNER) { - return ( - <View style={[pal.border, styles.childSpinner]}> - <ActivityIndicator /> - </View> - ) } else if (isThreadPost(item)) { const prev = isThreadPost(posts[index - 1]) ? (posts[index - 1] as ThreadPost) @@ -373,7 +321,9 @@ function PostThreadLoaded({ ? (posts[index - 1] as ThreadPost) : undefined const hasUnrevealedParents = - index === 0 && maxParents < skeleton.parents.length + index === 0 && + skeleton?.parents && + maxParents < skeleton.parents.length return ( <View ref={item.ctx.isHighlightedPost ? highlightedPostRef : undefined} @@ -390,9 +340,9 @@ function PostThreadLoaded({ showChildReplyLine={item.ctx.showChildReplyLine} showParentReplyLine={item.ctx.showParentReplyLine} hasPrecedingItem={ - !!prev?.ctx.showChildReplyLine || hasUnrevealedParents + !!prev?.ctx.showChildReplyLine || !!hasUnrevealedParents } - onPostReply={onRefresh} + onPostReply={refetch} /> </View> ) @@ -402,142 +352,62 @@ function PostThreadLoaded({ [ hasSession, isTabletOrMobile, + _, isMobile, onPressReply, pal.border, pal.viewLight, pal.textLight, - pal.view, - pal.text, - pal.colors.border, posts, - onRefresh, + skeleton?.parents, + maxParents, deferParents, treeView, - skeleton.parents.length, - maxParents, - _, + refetch, ], ) return ( - <List - ref={ref} - data={posts} - keyExtractor={item => item._reactKey} - renderItem={renderItem} - onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb} - onStartReached={onStartReached} - onMomentumScrollEnd={onMomentumScrollEnd} - onScrollToTop={onScrollToTop} - maintainVisibleContentPosition={ - isNative ? MAINTAIN_VISIBLE_CONTENT_POSITION : undefined - } - style={s.hContentRegion} - // @ts-ignore our .web version only -prf - desktopFixedHeight - removeClippedSubviews={isAndroid ? false : undefined} - windowSize={11} - /> - ) -} - -function PostThreadBlocked() { - const {_} = useLingui() - const pal = usePalette('default') - const navigation = useNavigation<NavigationProp>() - - const onPressBack = React.useCallback(() => { - if (navigation.canGoBack()) { - navigation.goBack() - } else { - navigation.navigate('Home') - } - }, [navigation]) - - return ( - <CenteredView> - <View style={[pal.view, pal.border, styles.notFoundContainer]}> - <Text type="title-lg" style={[pal.text, s.mb5]}> - <Trans>Post hidden</Trans> - </Text> - <Text type="md" style={[pal.text, s.mb10]}> - <Trans> - You have blocked the author or you have been blocked by the author. - </Trans> - </Text> - <TouchableOpacity - onPress={onPressBack} - accessibilityRole="button" - accessibilityLabel={_(msg`Back`)} - accessibilityHint=""> - <Text type="2xl" style={pal.link}> - <FontAwesomeIcon - icon="angle-left" - style={[pal.link as FontAwesomeIconStyle, s.mr5]} - size={14} + <> + <ListMaybePlaceholder + isLoading={!preferences || !thread} + isError={!!error} + onRetry={refetch} + errorTitle={error?.title} + errorMessage={error?.message} + /> + {!error && thread && ( + <List + ref={ref} + data={posts} + renderItem={renderItem} + keyExtractor={keyExtractor} + onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb} + onStartReached={onStartReached} + onEndReached={onEndReached} + onEndReachedThreshold={2} + onMomentumScrollEnd={onMomentumScrollEnd} + onScrollToTop={onScrollToTop} + maintainVisibleContentPosition={ + isNative ? MAINTAIN_VISIBLE_CONTENT_POSITION : undefined + } + // @ts-ignore our .web version only -prf + desktopFixedHeight + removeClippedSubviews={isAndroid ? false : undefined} + ListFooterComponent={ + <ListFooter + isFetching={isFetching} + onRetry={refetch} + // 300 is based on the minimum height of a post. This is enough extra height for the `maintainVisPos` to + // work without causing weird jumps on web or glitches on native + height={windowHeight - 200} /> - <Trans context="action">Back</Trans> - </Text> - </TouchableOpacity> - </View> - </CenteredView> - ) -} - -function PostThreadError({ - onRefresh, - notFound, - error, -}: { - onRefresh: () => void - notFound: boolean - error: Error | null -}) { - const {_} = useLingui() - const pal = usePalette('default') - const navigation = useNavigation<NavigationProp>() - - const onPressBack = React.useCallback(() => { - if (navigation.canGoBack()) { - navigation.goBack() - } else { - navigation.navigate('Home') - } - }, [navigation]) - - if (notFound) { - return ( - <CenteredView> - <View style={[pal.view, pal.border, styles.notFoundContainer]}> - <Text type="title-lg" style={[pal.text, s.mb5]}> - <Trans>Post not found</Trans> - </Text> - <Text type="md" style={[pal.text, s.mb10]}> - <Trans>The post may have been deleted.</Trans> - </Text> - <TouchableOpacity - onPress={onPressBack} - accessibilityRole="button" - accessibilityLabel={_(msg`Back`)} - accessibilityHint=""> - <Text type="2xl" style={pal.link}> - <FontAwesomeIcon - icon="angle-left" - style={[pal.link as FontAwesomeIconStyle, s.mr5]} - size={14} - /> - <Trans>Back</Trans> - </Text> - </TouchableOpacity> - </View> - </CenteredView> - ) - } - return ( - <CenteredView> - <ErrorMessage message={cleanError(error)} onPressTryAgain={onRefresh} /> - </CenteredView> + } + initialNumToRender={initialNumToRender} + windowSize={11} + /> + )} + </> ) } @@ -557,7 +427,9 @@ function createThreadSkeleton( node: ThreadNode, hasSession: boolean, treeView: boolean, -): ThreadSkeletonParts { +): ThreadSkeletonParts | null { + if (!node) return null + return { parents: Array.from(flattenThreadParents(node, hasSession)), highlightedPost: node, @@ -614,7 +486,10 @@ function hasPwiOptOut(node: ThreadPost) { return !!node.post.author.labels?.find(l => l.val === '!no-unauthenticated') } -function hasBranchingReplies(node: ThreadNode) { +function hasBranchingReplies(node?: ThreadNode) { + if (!node) { + return false + } if (node.type !== 'post') { return false } @@ -628,20 +503,9 @@ function hasBranchingReplies(node: ThreadNode) { } const styles = StyleSheet.create({ - notFoundContainer: { - margin: 10, - paddingHorizontal: 18, - paddingVertical: 14, - borderRadius: 6, - }, itemContainer: { borderTopWidth: 1, paddingHorizontal: 18, paddingVertical: 18, }, - childSpinner: { - borderTopWidth: 1, - paddingTop: 40, - paddingBottom: 200, - }, }) diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index cd746f9a9..6555bdf73 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -5,7 +5,7 @@ import { AppBskyFeedDefs, AppBskyFeedPost, RichText as RichTextAPI, - PostModeration, + ModerationDecision, } from '@atproto/api' import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' @@ -19,14 +19,14 @@ import {niceDate} from 'lib/strings/time' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {countLines, pluralize} from 'lib/strings/helpers' -import {isEmbedByEmbedder} from 'lib/embeds' import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers' import {PostMeta} from '../util/PostMeta' import {PostEmbeds} from '../util/post-embeds' import {PostCtrls} from '../util/post-ctrls/PostCtrls' -import {PostHider} from '../util/moderation/PostHider' -import {ContentHider} from '../util/moderation/ContentHider' -import {PostAlerts} from '../util/moderation/PostAlerts' +import {PostHider} from '../../../components/moderation/PostHider' +import {ContentHider} from '../../../components/moderation/ContentHider' +import {PostAlerts} from '../../../components/moderation/PostAlerts' +import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' import {ErrorMessage} from '../util/error/ErrorMessage' import {usePalette} from 'lib/hooks/usePalette' import {formatCount} from '../util/numeric/format' @@ -147,7 +147,7 @@ let PostThreadItemLoaded = ({ post: Shadow<AppBskyFeedDefs.PostView> record: AppBskyFeedPost.Record richText: RichTextAPI - moderation: PostModeration + moderation: ModerationDecision treeView: boolean depth: number prevPost: ThreadPost | undefined @@ -175,7 +175,6 @@ let PostThreadItemLoaded = ({ const itemTitle = _(msg`Post by ${post.author.handle}`) const authorHref = makeProfileLink(post.author) const authorTitle = post.author.handle - const isAuthorMuted = post.author.viewer?.muted const likesHref = React.useMemo(() => { const urip = new AtUri(post.uri) return makeProfileLink(post.author, 'post', urip.rkey, 'liked-by') @@ -206,11 +205,7 @@ let PostThreadItemLoaded = ({ uri: post.uri, cid: post.cid, text: record.text, - author: { - handle: post.author.handle, - displayName: post.author.displayName, - avatar: post.author.avatar, - }, + author: post.author, embed: post.embed, moderation, }, @@ -256,7 +251,8 @@ let PostThreadItemLoaded = ({ did={post.author.did} handle={post.author.handle} avatar={post.author.avatar} - moderation={moderation.avatar} + moderation={moderation.ui('avatar')} + type={post.author.associated?.labeler ? 'labeler' : 'user'} /> </View> <View style={styles.layoutContent}> @@ -271,35 +267,12 @@ let PostThreadItemLoaded = ({ {sanitizeDisplayName( post.author.displayName || sanitizeHandle(post.author.handle), + moderation.ui('displayName'), )} </Text> </Link> </View> <View style={styles.meta}> - {isAuthorMuted && ( - <View - style={[ - pal.viewLight, - { - flexDirection: 'row', - alignItems: 'center', - gap: 4, - borderRadius: 6, - paddingHorizontal: 6, - paddingVertical: 2, - marginRight: 4, - }, - ]}> - <FontAwesomeIcon - icon={['far', 'eye-slash']} - size={12} - color={pal.colors.textLight} - /> - <Text type="sm-medium" style={pal.textLight}> - Muted - </Text> - </View> - )} <Link style={s.flex1} href={authorHref} title={authorTitle}> <Text type="md" style={[pal.textLight]} numberOfLines={1}> {sanitizeHandle(post.author.handle, '@')} @@ -312,15 +285,16 @@ let PostThreadItemLoaded = ({ )} </View> <View style={[s.pl10, s.pr10, s.pb10]}> + <LabelsOnMyPost post={post} /> <ContentHider - moderation={moderation.content} + modui={moderation.ui('contentView')} ignoreMute style={styles.contentHider} childContainerStyle={styles.contentHiderChild}> <PostAlerts - moderation={moderation.content} + modui={moderation.ui('contentView')} includeMute - style={styles.alert} + style={[a.pt_2xs, a.pb_sm]} /> {richText?.text ? ( <View @@ -338,18 +312,9 @@ let PostThreadItemLoaded = ({ </View> ) : undefined} {post.embed && ( - <ContentHider - moderation={moderation.embed} - moderationDecisions={moderation.decisions} - ignoreMute={isEmbedByEmbedder(post.embed, post.author.did)} - ignoreQuoteDecisions - style={s.mb10}> - <PostEmbeds - embed={post.embed} - moderation={moderation.embed} - moderationDecisions={moderation.decisions} - /> - </ContentHider> + <View style={[a.pb_sm]}> + <PostEmbeds embed={post.embed} moderation={moderation} /> + </View> )} </ContentHider> <ExpandedPostDetails @@ -432,7 +397,8 @@ let PostThreadItemLoaded = ({ <PostHider testID={`postThreadItem-by-${post.author.handle}`} href={postHref} - moderation={moderation.content} + style={[pal.view]} + modui={moderation.ui('contentList')} iconSize={isThreadedChild ? 26 : 38} iconStyles={ isThreadedChild @@ -482,7 +448,8 @@ let PostThreadItemLoaded = ({ did={post.author.did} handle={post.author.handle} avatar={post.author.avatar} - moderation={moderation.avatar} + moderation={moderation.ui('avatar')} + type={post.author.associated?.labeler ? 'labeler' : 'user'} /> {showChildReplyLine && ( @@ -508,19 +475,21 @@ let PostThreadItemLoaded = ({ }> <PostMeta author={post.author} + moderation={moderation} authorHasWarning={!!post.author.labels?.length} timestamp={post.indexedAt} postHref={postHref} showAvatar={isThreadedChild} - avatarModeration={moderation.avatar} + avatarModeration={moderation.ui('avatar')} avatarSize={28} displayNameType="md-bold" displayNameStyle={isThreadedChild && s.ml2} style={isThreadedChild && s.mb2} /> + <LabelsOnMyPost post={post} /> <PostAlerts - moderation={moderation.content} - style={styles.alert} + modui={moderation.ui('contentList')} + style={[a.pt_xs, a.pb_sm]} /> {richText?.text ? ( <View style={styles.postTextContainer}> @@ -542,18 +511,9 @@ let PostThreadItemLoaded = ({ /> ) : undefined} {post.embed && ( - <ContentHider - style={styles.contentHider} - moderation={moderation.embed} - moderationDecisions={moderation.decisions} - ignoreMute={isEmbedByEmbedder(post.embed, post.author.did)} - ignoreQuoteDecisions> - <PostEmbeds - embed={post.embed} - moderation={moderation.embed} - moderationDecisions={moderation.decisions} - /> - </ContentHider> + <View style={[a.pb_xs]}> + <PostEmbeds embed={post.embed} moderation={moderation} /> + </View> )} <PostCtrls post={post} @@ -578,7 +538,7 @@ let PostThreadItemLoaded = ({ title={itemTitle} noFeedback> <Text type="sm-medium" style={pal.textLight}> - More + <Trans>More</Trans> </Text> <FontAwesomeIcon icon="angle-right" diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index 7e53eb271..47e46eb0c 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -4,7 +4,7 @@ import { AppBskyFeedDefs, AppBskyFeedPost, AtUri, - PostModeration, + ModerationDecision, RichText as RichTextAPI, } from '@atproto/api' import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' @@ -14,8 +14,9 @@ import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' import {PostEmbeds} from '../util/post-embeds' import {PostCtrls} from '../util/post-ctrls/PostCtrls' -import {ContentHider} from '../util/moderation/ContentHider' -import {PostAlerts} from '../util/moderation/PostAlerts' +import {ContentHider} from '../../../components/moderation/ContentHider' +import {PostAlerts} from '../../../components/moderation/PostAlerts' +import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' import {Text} from '../util/text/Text' import {RichText} from '#/components/RichText' import {PreviewableUserAvatar} from '../util/UserAvatar' @@ -93,7 +94,7 @@ function PostInner({ post: Shadow<AppBskyFeedDefs.PostView> record: AppBskyFeedPost.Record richText: RichTextAPI - moderation: PostModeration + moderation: ModerationDecision showReplyLine?: boolean style?: StyleProp<ViewStyle> }) { @@ -117,11 +118,7 @@ function PostInner({ uri: post.uri, cid: post.cid, text: record.text, - author: { - handle: post.author.handle, - displayName: post.author.displayName, - avatar: post.author.avatar, - }, + author: post.author, embed: post.embed, moderation, }, @@ -142,12 +139,14 @@ function PostInner({ did={post.author.did} handle={post.author.handle} avatar={post.author.avatar} - moderation={moderation.avatar} + moderation={moderation.ui('avatar')} + type={post.author.associated?.labeler ? 'labeler' : 'user'} /> </View> <View style={styles.layoutContent}> <PostMeta author={post.author} + moderation={moderation} authorHasWarning={!!post.author.labels?.length} timestamp={post.indexedAt} postHref={itemHref} @@ -176,11 +175,15 @@ function PostInner({ </Text> </View> )} + <LabelsOnMyPost post={post} /> <ContentHider - moderation={moderation.content} + modui={moderation.ui('contentView')} style={styles.contentHider} childContainerStyle={styles.contentHiderChild}> - <PostAlerts moderation={moderation.content} style={styles.alert} /> + <PostAlerts + modui={moderation.ui('contentView')} + style={[a.py_xs]} + /> {richText.text ? ( <View style={styles.postTextContainer}> <RichText @@ -202,17 +205,7 @@ function PostInner({ /> ) : undefined} {post.embed ? ( - <ContentHider - moderation={moderation.embed} - moderationDecisions={moderation.decisions} - ignoreQuoteDecisions - style={styles.contentHider}> - <PostEmbeds - embed={post.embed} - moderation={moderation.embed} - moderationDecisions={moderation.decisions} - /> - </ContentHider> + <PostEmbeds embed={post.embed} moderation={moderation} /> ) : null} </ContentHider> <PostCtrls diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index f3911da60..0fbcc4a13 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -4,7 +4,7 @@ import { AppBskyFeedDefs, AppBskyFeedPost, AtUri, - PostModeration, + ModerationDecision, RichText as RichTextAPI, } from '@atproto/api' import { @@ -18,8 +18,9 @@ import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' import {PostCtrls} from '../util/post-ctrls/PostCtrls' import {PostEmbeds} from '../util/post-embeds' -import {ContentHider} from '../util/moderation/ContentHider' -import {PostAlerts} from '../util/moderation/PostAlerts' +import {ContentHider} from '#/components/moderation/ContentHider' +import {PostAlerts} from '../../../components/moderation/PostAlerts' +import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' import {RichText} from '#/components/RichText' import {PreviewableUserAvatar} from '../util/UserAvatar' import {s} from 'lib/styles' @@ -27,13 +28,11 @@ import {usePalette} from 'lib/hooks/usePalette' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {makeProfileLink} from 'lib/routes/links' -import {isEmbedByEmbedder} from 'lib/embeds' import {MAX_POST_LINES} from 'lib/constants' import {countLines} from 'lib/strings/helpers' import {useComposerControls} from '#/state/shell/composer' import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' import {FeedNameText} from '../util/FeedInfoText' -import {useSession} from '#/state/session' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {atoms as a} from '#/alf' @@ -50,7 +49,7 @@ export function FeedItem({ post: AppBskyFeedDefs.PostView record: AppBskyFeedPost.Record reason: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource | undefined - moderation: PostModeration + moderation: ModerationDecision isThreadChild?: boolean isThreadLastChild?: boolean isThreadParent?: boolean @@ -100,7 +99,7 @@ let FeedItemInner = ({ record: AppBskyFeedPost.Record reason: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource | undefined richText: RichTextAPI - moderation: PostModeration + moderation: ModerationDecision isThreadChild?: boolean isThreadLastChild?: boolean isThreadParent?: boolean @@ -108,14 +107,10 @@ let FeedItemInner = ({ const {openComposer} = useComposerControls() const pal = usePalette('default') const {_} = useLingui() - const {currentAccount} = useSession() const href = useMemo(() => { const urip = new AtUri(post.uri) return makeProfileLink(post.author, 'post', urip.rkey) }, [post.uri, post.author]) - const isModeratedPost = - moderation.decisions.post.cause?.type === 'label' && - moderation.decisions.post.cause.label.src !== currentAccount?.did const replyAuthorDid = useMemo(() => { if (!record?.reply) { @@ -131,11 +126,7 @@ let FeedItemInner = ({ uri: post.uri, cid: post.cid, text: record.text || '', - author: { - handle: post.author.handle, - displayName: post.author.displayName, - avatar: post.author.avatar, - }, + author: post.author, embed: post.embed, moderation, }, @@ -148,7 +139,7 @@ let FeedItemInner = ({ borderColor: pal.colors.border, paddingBottom: isThreadLastChild || (!isThreadChild && !isThreadParent) - ? 6 + ? 8 : undefined, }, isThreadChild ? styles.outerSmallTop : undefined, @@ -229,6 +220,7 @@ let FeedItemInner = ({ numberOfLines={1} text={sanitizeDisplayName( reason.by.displayName || sanitizeHandle(reason.by.handle), + moderation.ui('displayName'), )} href={makeProfileLink(reason.by)} /> @@ -246,7 +238,8 @@ let FeedItemInner = ({ did={post.author.did} handle={post.author.handle} avatar={post.author.avatar} - moderation={moderation.avatar} + moderation={moderation.ui('avatar')} + type={post.author.associated?.labeler ? 'labeler' : 'user'} /> {isThreadParent && ( <View @@ -264,6 +257,7 @@ let FeedItemInner = ({ <View style={styles.layoutContent}> <PostMeta author={post.author} + moderation={moderation} authorHasWarning={!!post.author.labels?.length} timestamp={post.indexedAt} postHref={href} @@ -295,6 +289,7 @@ let FeedItemInner = ({ </Text> </View> )} + <LabelsOnMyPost post={post} /> <PostContent moderation={moderation} richText={richText} @@ -306,9 +301,6 @@ let FeedItemInner = ({ record={record} richText={richText} onPressReply={onPressReply} - showAppealLabelItem={ - post.author.did === currentAccount?.did && isModeratedPost - } logContext="FeedItem" /> </View> @@ -324,7 +316,7 @@ let PostContent = ({ postEmbed, postAuthor, }: { - moderation: PostModeration + moderation: ModerationDecision richText: RichTextAPI postEmbed: AppBskyFeedDefs.PostView['embed'] postAuthor: AppBskyFeedDefs.PostView['author'] @@ -342,10 +334,10 @@ let PostContent = ({ return ( <ContentHider testID="contentHider-post" - moderation={moderation.content} + modui={moderation.ui('contentList')} ignoreMute childContainerStyle={styles.contentHiderChild}> - <PostAlerts moderation={moderation.content} style={styles.alert} /> + <PostAlerts modui={moderation.ui('contentList')} style={[a.py_xs]} /> {richText.text ? ( <View style={styles.postTextContainer}> <RichText @@ -367,19 +359,9 @@ let PostContent = ({ /> ) : undefined} {postEmbed ? ( - <ContentHider - testID="contentHider-embed" - moderation={moderation.embed} - moderationDecisions={moderation.decisions} - ignoreMute={isEmbedByEmbedder(postEmbed, postAuthor.did)} - ignoreQuoteDecisions - style={styles.embed}> - <PostEmbeds - embed={postEmbed} - moderation={moderation.embed} - moderationDecisions={moderation.decisions} - /> - </ContentHider> + <View style={[a.pb_sm]}> + <PostEmbeds embed={postEmbed} moderation={moderation} /> + </View> ) : null} </ContentHider> ) diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 019e6c10e..235139fff 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -3,7 +3,8 @@ import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import { AppBskyActorDefs, moderateProfile, - ProfileModeration, + ModerationCause, + ModerationDecision, } from '@atproto/api' import {Link} from '../util/Link' import {Text} from '../util/text/Text' @@ -14,16 +15,13 @@ import {FollowButton} from './FollowButton' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {makeProfileLink} from 'lib/routes/links' -import { - describeModerationCause, - getProfileModerationCauses, - getModerationCauseKey, -} from 'lib/moderation' +import {getModerationCauseKey, isJustAMute} from 'lib/moderation' import {Shadow} from '#/state/cache/types' import {useModerationOpts} from '#/state/queries/preferences' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useSession} from '#/state/session' import {Trans} from '@lingui/macro' +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' export function ProfileCard({ testID, @@ -33,6 +31,7 @@ export function ProfileCard({ noBorder, followers, renderButton, + onPress, style, }: { testID?: string @@ -44,20 +43,19 @@ export function ProfileCard({ renderButton?: ( profile: Shadow<AppBskyActorDefs.ProfileViewBasic>, ) => React.ReactNode + onPress?: () => void style?: StyleProp<ViewStyle> }) { const pal = usePalette('default') const profile = useProfileShadow(profileUnshadowed) const moderationOpts = useModerationOpts() + const isLabeler = profile?.associated?.labeler if (!moderationOpts) { return null } const moderation = moderateProfile(profile, moderationOpts) - if ( - !noModFilter && - moderation.account.filter && - moderation.account.cause?.type !== 'muted' - ) { + const modui = moderation.ui('profileList') + if (!noModFilter && modui.filter && !isJustAMute(modui)) { return null } @@ -73,6 +71,7 @@ export function ProfileCard({ ]} href={makeProfileLink(profile)} title={profile.handle} + onBeforePress={onPress} asAnchor anchorNoUnderline> <View style={styles.layout}> @@ -80,7 +79,8 @@ export function ProfileCard({ <UserAvatar size={40} avatar={profile.avatar} - moderation={moderation.avatar} + moderation={moderation.ui('avatar')} + type={isLabeler ? 'labeler' : 'user'} /> </View> <View style={styles.layoutContent}> @@ -91,7 +91,7 @@ export function ProfileCard({ lineHeight={1.2}> {sanitizeDisplayName( profile.displayName || sanitizeHandle(profile.handle), - moderation.profile, + moderation.ui('displayName'), )} </Text> <Text type="md" style={[pal.textLight]} numberOfLines={1}> @@ -103,7 +103,7 @@ export function ProfileCard({ /> {!!profile.viewer?.followedBy && <View style={s.flexRow} />} </View> - {renderButton ? ( + {renderButton && !isLabeler ? ( <View style={styles.layoutButton}>{renderButton(profile)}</View> ) : undefined} </View> @@ -119,17 +119,17 @@ export function ProfileCard({ ) } -function ProfileCardPills({ +export function ProfileCardPills({ followedBy, moderation, }: { followedBy: boolean - moderation: ProfileModeration + moderation: ModerationDecision }) { const pal = usePalette('default') - const causes = getProfileModerationCauses(moderation) - if (!followedBy && !causes.length) { + const modui = moderation.ui('profileList') + if (!followedBy && !modui.inform && !modui.alert) { return null } @@ -142,19 +142,41 @@ function ProfileCardPills({ </Text> </View> )} - {causes.map(cause => { - const desc = describeModerationCause(cause, 'account') - return ( - <View - style={[s.mt5, pal.btn, styles.pill]} - key={getModerationCauseKey(cause)}> - <Text type="xs" style={pal.text}> - {cause?.type === 'label' ? 'âš ' : ''} - {desc.name} - </Text> - </View> - ) - })} + {modui.alerts.map(alert => ( + <ProfileCardPillModerationCause + key={getModerationCauseKey(alert)} + cause={alert} + severity="alert" + /> + ))} + {modui.informs.map(inform => ( + <ProfileCardPillModerationCause + key={getModerationCauseKey(inform)} + cause={inform} + severity="inform" + /> + ))} + </View> + ) +} + +function ProfileCardPillModerationCause({ + cause, + severity, +}: { + cause: ModerationCause + severity: 'alert' | 'inform' +}) { + const pal = usePalette('default') + const {name} = useModerationCauseDescription(cause) + return ( + <View + style={[s.mt5, pal.btn, styles.pill]} + key={getModerationCauseKey(cause)}> + <Text type="xs" style={pal.text}> + {severity === 'alert' ? 'âš ' : ''} + {name} + </Text> </View> ) } @@ -177,7 +199,7 @@ function FollowersList({ f, mod: moderateProfile(f, moderationOpts), })) - .filter(({mod}) => !mod.account.filter) + .filter(({mod}) => !mod.ui('profileList').filter) }, [followers, moderationOpts]) if (!followersWithMods?.length) { @@ -199,7 +221,12 @@ function FollowersList({ {followersWithMods.slice(0, 3).map(({f, mod}) => ( <View key={f.did} style={styles.followedByAviContainer}> <View style={[styles.followedByAvi, pal.view]}> - <UserAvatar avatar={f.avatar} size={32} moderation={mod.avatar} /> + <UserAvatar + avatar={f.avatar} + size={32} + moderation={mod.ui('avatar')} + type={f.associated?.labeler ? 'labeler' : 'user'} + /> </View> </View> ))} @@ -212,11 +239,13 @@ export function ProfileCardWithFollowBtn({ noBg, noBorder, followers, + onPress, }: { profile: AppBskyActorDefs.ProfileViewBasic noBg?: boolean noBorder?: boolean followers?: AppBskyActorDefs.ProfileView[] | undefined + onPress?: () => void }) { const {currentAccount} = useSession() const isMe = profile.did === currentAccount?.did @@ -234,6 +263,7 @@ export function ProfileCardWithFollowBtn({ <FollowButton profile={profileShadow} logContext="ProfileCard" /> ) } + onPress={onPress} /> ) } diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx index 411ae6c17..b11a33f27 100644 --- a/src/view/com/profile/ProfileFollowers.tsx +++ b/src/view/com/profile/ProfileFollowers.tsx @@ -1,39 +1,66 @@ import React from 'react' -import {ActivityIndicator, StyleSheet, View} from 'react-native' import {AppBskyActorDefs as ActorDefs} from '@atproto/api' -import {CenteredView} from '../util/Views' -import {LoadingScreen} from '../util/LoadingScreen' import {List} from '../util/List' -import {ErrorMessage} from '../util/error/ErrorMessage' import {ProfileCardWithFollowBtn} from './ProfileCard' import {useProfileFollowersQuery} from '#/state/queries/profile-followers' import {useResolveDidQuery} from '#/state/queries/resolve-uri' import {logger} from '#/logger' import {cleanError} from '#/lib/strings/errors' +import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' +import { + ListFooter, + ListHeaderDesktop, + ListMaybePlaceholder, +} from '#/components/Lists' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useSession} from 'state/session' +import {View} from 'react-native' + +function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) { + return <ProfileCardWithFollowBtn key={item.did} profile={item} /> +} + +function keyExtractor(item: ActorDefs.ProfileViewBasic) { + return item.did +} export function ProfileFollowers({name}: {name: string}) { + const {_} = useLingui() + const initialNumToRender = useInitialNumToRender() + const {currentAccount} = useSession() + const [isPTRing, setIsPTRing] = React.useState(false) const { data: resolvedDid, + isLoading: isDidLoading, error: resolveError, - isFetching: isFetchingDid, } = useResolveDidQuery(name) const { data, + isLoading: isFollowersLoading, isFetching, - isFetched, isFetchingNextPage, hasNextPage, fetchNextPage, - isError, error, refetch, } = useProfileFollowersQuery(resolvedDid) + const isError = React.useMemo( + () => !!resolveError || !!error, + [resolveError, error], + ) + + const isMe = React.useMemo(() => { + return resolvedDid === currentAccount?.did + }, [resolvedDid, currentAccount?.did]) + const followers = React.useMemo(() => { if (data?.pages) { return data.pages.flatMap(page => page.followers) } + return [] }, [data]) const onRefresh = React.useCallback(async () => { @@ -47,7 +74,7 @@ export function ProfileFollowers({name}: {name: string}) { }, [refetch, setIsPTRing]) const onEndReached = async () => { - if (isFetching || !hasNextPage || isError) return + if (isFetching || !hasNextPage || !!error) return try { await fetchNextPage() } catch (err) { @@ -55,57 +82,38 @@ export function ProfileFollowers({name}: {name: string}) { } } - const renderItem = React.useCallback( - ({item}: {item: ActorDefs.ProfileViewBasic}) => ( - <ProfileCardWithFollowBtn key={item.did} profile={item} /> - ), - [], - ) - - if (isFetchingDid || !isFetched) { - return <LoadingScreen /> - } - - // error - // = - if (resolveError || isError) { - return ( - <CenteredView> - <ErrorMessage - message={cleanError(resolveError || error)} - onPressTryAgain={onRefresh} - /> - </CenteredView> - ) - } - - // loaded - // = return ( - <List - data={followers} - keyExtractor={item => item.did} - refreshing={isPTRing} - onRefresh={onRefresh} - onEndReached={onEndReached} - renderItem={renderItem} - initialNumToRender={15} - // FIXME(dan) - // eslint-disable-next-line react/no-unstable-nested-components - ListFooterComponent={() => ( - <View style={styles.footer}> - {(isFetching || isFetchingNextPage) && <ActivityIndicator />} - </View> + <View style={{flex: 1}}> + <ListMaybePlaceholder + isLoading={isDidLoading || isFollowersLoading} + isEmpty={followers.length < 1} + isError={isError} + emptyType="results" + emptyMessage={ + isMe + ? _(msg`You do not have any followers.`) + : _(msg`This user doesn't have any followers.`) + } + errorMessage={cleanError(resolveError || error)} + onRetry={isError ? refetch : undefined} + /> + {followers.length > 0 && ( + <List + data={followers} + renderItem={renderItem} + keyExtractor={keyExtractor} + refreshing={isPTRing} + onRefresh={onRefresh} + onEndReached={onEndReached} + onEndReachedThreshold={4} + ListHeaderComponent={<ListHeaderDesktop title={_(msg`Followers`)} />} + ListFooterComponent={<ListFooter isFetching={isFetchingNextPage} />} + // @ts-ignore our .web version only -prf + desktopFixedHeight + initialNumToRender={initialNumToRender} + windowSize={11} + /> )} - // @ts-ignore our .web version only -prf - desktopFixedHeight - /> + </View> ) } - -const styles = StyleSheet.create({ - footer: { - height: 200, - paddingTop: 20, - }, -}) diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx index bd4af1081..d99e2b840 100644 --- a/src/view/com/profile/ProfileFollows.tsx +++ b/src/view/com/profile/ProfileFollows.tsx @@ -1,39 +1,65 @@ import React from 'react' -import {ActivityIndicator, StyleSheet, View} from 'react-native' import {AppBskyActorDefs as ActorDefs} from '@atproto/api' -import {CenteredView} from '../util/Views' -import {LoadingScreen} from '../util/LoadingScreen' import {List} from '../util/List' -import {ErrorMessage} from '../util/error/ErrorMessage' import {ProfileCardWithFollowBtn} from './ProfileCard' import {useProfileFollowsQuery} from '#/state/queries/profile-follows' import {useResolveDidQuery} from '#/state/queries/resolve-uri' import {logger} from '#/logger' import {cleanError} from '#/lib/strings/errors' +import { + ListFooter, + ListHeaderDesktop, + ListMaybePlaceholder, +} from '#/components/Lists' +import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' +import {useSession} from 'state/session' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) { + return <ProfileCardWithFollowBtn key={item.did} profile={item} /> +} + +function keyExtractor(item: ActorDefs.ProfileViewBasic) { + return item.did +} export function ProfileFollows({name}: {name: string}) { + const {_} = useLingui() + const initialNumToRender = useInitialNumToRender() + const {currentAccount} = useSession() + const [isPTRing, setIsPTRing] = React.useState(false) const { data: resolvedDid, + isLoading: isDidLoading, error: resolveError, - isFetching: isFetchingDid, } = useResolveDidQuery(name) const { data, + isLoading: isFollowsLoading, isFetching, - isFetched, isFetchingNextPage, hasNextPage, fetchNextPage, - isError, error, refetch, } = useProfileFollowsQuery(resolvedDid) + const isError = React.useMemo( + () => !!resolveError || !!error, + [resolveError, error], + ) + + const isMe = React.useMemo(() => { + return resolvedDid === currentAccount?.did + }, [resolvedDid, currentAccount?.did]) + const follows = React.useMemo(() => { if (data?.pages) { return data.pages.flatMap(page => page.follows) } + return [] }, [data]) const onRefresh = React.useCallback(async () => { @@ -47,7 +73,7 @@ export function ProfileFollows({name}: {name: string}) { }, [refetch, setIsPTRing]) const onEndReached = async () => { - if (isFetching || !hasNextPage || isError) return + if (isFetching || !hasNextPage || !!error) return try { await fetchNextPage() } catch (err) { @@ -55,57 +81,38 @@ export function ProfileFollows({name}: {name: string}) { } } - const renderItem = React.useCallback( - ({item}: {item: ActorDefs.ProfileViewBasic}) => ( - <ProfileCardWithFollowBtn key={item.did} profile={item} /> - ), - [], - ) - - if (isFetchingDid || !isFetched) { - return <LoadingScreen /> - } - - // error - // = - if (resolveError || isError) { - return ( - <CenteredView> - <ErrorMessage - message={cleanError(resolveError || error)} - onPressTryAgain={onRefresh} - /> - </CenteredView> - ) - } - - // loaded - // = return ( - <List - data={follows} - keyExtractor={item => item.did} - refreshing={isPTRing} - onRefresh={onRefresh} - onEndReached={onEndReached} - renderItem={renderItem} - initialNumToRender={15} - // FIXME(dan) - // eslint-disable-next-line react/no-unstable-nested-components - ListFooterComponent={() => ( - <View style={styles.footer}> - {(isFetching || isFetchingNextPage) && <ActivityIndicator />} - </View> + <> + <ListMaybePlaceholder + isLoading={isDidLoading || isFollowsLoading} + isEmpty={follows.length < 1} + isError={isError} + emptyType="results" + emptyMessage={ + isMe + ? _(msg`You are not following anyone.`) + : _(msg`This user isn't following anyone.`) + } + errorMessage={cleanError(resolveError || error)} + onRetry={isError ? refetch : undefined} + /> + {follows.length > 0 && ( + <List + data={follows} + renderItem={renderItem} + keyExtractor={keyExtractor} + refreshing={isPTRing} + onRefresh={onRefresh} + onEndReached={onEndReached} + onEndReachedThreshold={4} + ListHeaderComponent={<ListHeaderDesktop title={_(msg`Following`)} />} + ListFooterComponent={<ListFooter isFetching={isFetchingNextPage} />} + // @ts-ignore our .web version only -prf + desktopFixedHeight + initialNumToRender={initialNumToRender} + windowSize={11} + /> )} - // @ts-ignore our .web version only -prf - desktopFixedHeight - /> + </> ) } - -const styles = StyleSheet.create({ - footer: { - height: 200, - paddingTop: 20, - }, -}) diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx deleted file mode 100644 index 17dc5ce1b..000000000 --- a/src/view/com/profile/ProfileHeader.tsx +++ /dev/null @@ -1,598 +0,0 @@ -import React, {memo, useMemo} from 'react' -import { - StyleSheet, - TouchableOpacity, - TouchableWithoutFeedback, - View, -} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {useNavigation} from '@react-navigation/native' -import { - AppBskyActorDefs, - ModerationOpts, - moderateProfile, - RichText as RichTextAPI, -} from '@atproto/api' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {NavigationProp} from 'lib/routes/types' -import {isNative} from 'platform/detection' -import {BlurView} from '../util/BlurView' -import * as Toast from '../util/Toast' -import {LoadingPlaceholder} from '../util/LoadingPlaceholder' -import {Text} from '../util/text/Text' -import {ThemedText} from '../util/text/ThemedText' -import {RichText} from '#/components/RichText' -import {UserAvatar} from '../util/UserAvatar' -import {UserBanner} from '../util/UserBanner' -import {ProfileHeaderAlerts} from '../util/moderation/ProfileHeaderAlerts' -import {formatCount} from '../util/numeric/format' -import {Link} from '../util/Link' -import {ProfileHeaderSuggestedFollows} from './ProfileHeaderSuggestedFollows' -import {useModalControls} from '#/state/modals' -import {useLightboxControls, ProfileImageLightbox} from '#/state/lightbox' -import { - useProfileBlockMutationQueue, - useProfileFollowMutationQueue, -} from '#/state/queries/profile' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics/analytics' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {BACK_HITSLOP} from 'lib/constants' -import {isInvalidHandle, sanitizeHandle} from 'lib/strings/handles' -import {makeProfileLink} from 'lib/routes/links' -import {pluralize} from 'lib/strings/helpers' -import {sanitizeDisplayName} from 'lib/strings/display-names' -import {s, colors} from 'lib/styles' -import {logger} from '#/logger' -import {useSession} from '#/state/session' -import {Shadow} from '#/state/cache/types' -import {useRequireAuth} from '#/state/session' -import {LabelInfo} from '../util/moderation/LabelInfo' -import {useProfileShadow} from 'state/cache/profile-shadow' -import {atoms as a} from '#/alf' -import {ProfileMenu} from 'view/com/profile/ProfileMenu' -import * as Prompt from '#/components/Prompt' - -let ProfileHeaderLoading = (_props: {}): React.ReactNode => { - const pal = usePalette('default') - return ( - <View style={pal.view}> - <LoadingPlaceholder width="100%" height={150} style={{borderRadius: 0}} /> - <View - style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}> - <LoadingPlaceholder width={80} height={80} style={styles.br40} /> - </View> - <View style={styles.content}> - <View style={[styles.buttonsLine]}> - <LoadingPlaceholder width={167} height={31} style={styles.br50} /> - </View> - </View> - </View> - ) -} -ProfileHeaderLoading = memo(ProfileHeaderLoading) -export {ProfileHeaderLoading} - -interface Props { - profile: AppBskyActorDefs.ProfileViewDetailed - descriptionRT: RichTextAPI | null - moderationOpts: ModerationOpts - hideBackButton?: boolean - isPlaceholderProfile?: boolean -} - -let ProfileHeader = ({ - profile: profileUnshadowed, - descriptionRT, - moderationOpts, - hideBackButton = false, - isPlaceholderProfile, -}: Props): React.ReactNode => { - const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> = - useProfileShadow(profileUnshadowed) - const pal = usePalette('default') - const palInverted = usePalette('inverted') - const {currentAccount, hasSession} = useSession() - const requireAuth = useRequireAuth() - const {_} = useLingui() - const {openModal} = useModalControls() - const {openLightbox} = useLightboxControls() - const navigation = useNavigation<NavigationProp>() - const {track} = useAnalytics() - const invalidHandle = isInvalidHandle(profile.handle) - const {isDesktop} = useWebMediaQueries() - const [showSuggestedFollows, setShowSuggestedFollows] = React.useState(false) - const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( - profile, - 'ProfileHeader', - ) - const [__, queueUnblock] = useProfileBlockMutationQueue(profile) - const unblockPromptControl = Prompt.usePromptControl() - const moderation = useMemo( - () => moderateProfile(profile, moderationOpts), - [profile, moderationOpts], - ) - - const onPressBack = React.useCallback(() => { - if (navigation.canGoBack()) { - navigation.goBack() - } else { - navigation.navigate('Home') - } - }, [navigation]) - - const onPressAvi = React.useCallback(() => { - if ( - profile.avatar && - !(moderation.avatar.blur && moderation.avatar.noOverride) - ) { - openLightbox(new ProfileImageLightbox(profile)) - } - }, [openLightbox, profile, moderation]) - - const onPressFollow = () => { - requireAuth(async () => { - try { - track('ProfileHeader:FollowButtonClicked') - await queueFollow() - Toast.show( - _( - msg`Following ${sanitizeDisplayName( - profile.displayName || profile.handle, - )}`, - ), - ) - } catch (e: any) { - if (e?.name !== 'AbortError') { - logger.error('Failed to follow', {message: String(e)}) - Toast.show(_(msg`There was an issue! ${e.toString()}`)) - } - } - }) - } - - const onPressUnfollow = () => { - requireAuth(async () => { - try { - track('ProfileHeader:UnfollowButtonClicked') - await queueUnfollow() - Toast.show( - _( - msg`No longer following ${sanitizeDisplayName( - profile.displayName || profile.handle, - )}`, - ), - ) - } catch (e: any) { - if (e?.name !== 'AbortError') { - logger.error('Failed to unfollow', {message: String(e)}) - Toast.show(_(msg`There was an issue! ${e.toString()}`)) - } - } - }) - } - - const onPressEditProfile = React.useCallback(() => { - track('ProfileHeader:EditProfileButtonClicked') - openModal({ - name: 'edit-profile', - profile, - }) - }, [track, openModal, profile]) - - const unblockAccount = React.useCallback(async () => { - track('ProfileHeader:UnblockAccountButtonClicked') - try { - await queueUnblock() - Toast.show(_(msg`Account unblocked`)) - } catch (e: any) { - if (e?.name !== 'AbortError') { - logger.error('Failed to unblock account', {message: e}) - Toast.show(_(msg`There was an issue! ${e.toString()}`)) - } - } - }, [_, queueUnblock, track]) - - const isMe = React.useMemo( - () => currentAccount?.did === profile.did, - [currentAccount, profile], - ) - - const blockHide = - !isMe && (profile.viewer?.blocking || profile.viewer?.blockedBy) - const following = formatCount(profile.followsCount || 0) - const followers = formatCount(profile.followersCount || 0) - const pluralizedFollowers = pluralize(profile.followersCount || 0, 'follower') - - return ( - <View style={[pal.view]} pointerEvents="box-none"> - <View pointerEvents="none"> - {isPlaceholderProfile ? ( - <LoadingPlaceholder - width="100%" - height={150} - style={{borderRadius: 0}} - /> - ) : ( - <UserBanner banner={profile.banner} moderation={moderation.avatar} /> - )} - </View> - <View style={styles.content} pointerEvents="box-none"> - <View style={[styles.buttonsLine]} pointerEvents="box-none"> - {isMe ? ( - <TouchableOpacity - testID="profileHeaderEditProfileButton" - onPress={onPressEditProfile} - style={[styles.btn, styles.mainBtn, pal.btn]} - accessibilityRole="button" - accessibilityLabel={_(msg`Edit profile`)} - accessibilityHint={_( - msg`Opens editor for profile display name, avatar, background image, and description`, - )}> - <Text type="button" style={pal.text}> - <Trans>Edit Profile</Trans> - </Text> - </TouchableOpacity> - ) : profile.viewer?.blocking ? ( - profile.viewer?.blockingByList ? null : ( - <TouchableOpacity - testID="unblockBtn" - onPress={() => unblockPromptControl.open()} - style={[styles.btn, styles.mainBtn, pal.btn]} - accessibilityRole="button" - accessibilityLabel={_(msg`Unblock`)} - accessibilityHint=""> - <Text type="button" style={[pal.text, s.bold]}> - <Trans context="action">Unblock</Trans> - </Text> - </TouchableOpacity> - ) - ) : !profile.viewer?.blockedBy ? ( - <> - {hasSession && ( - <TouchableOpacity - testID="suggestedFollowsBtn" - onPress={() => setShowSuggestedFollows(!showSuggestedFollows)} - style={[ - styles.btn, - styles.mainBtn, - pal.btn, - { - paddingHorizontal: 10, - backgroundColor: showSuggestedFollows - ? pal.colors.text - : pal.colors.backgroundLight, - }, - ]} - accessibilityRole="button" - accessibilityLabel={_( - msg`Show follows similar to ${profile.handle}`, - )} - accessibilityHint={_( - msg`Shows a list of users similar to this user.`, - )}> - <FontAwesomeIcon - icon="user-plus" - style={[ - pal.text, - { - color: showSuggestedFollows - ? pal.textInverted.color - : pal.text.color, - }, - ]} - size={14} - /> - </TouchableOpacity> - )} - - {profile.viewer?.following ? ( - <TouchableOpacity - testID="unfollowBtn" - onPress={onPressUnfollow} - style={[styles.btn, styles.mainBtn, pal.btn]} - accessibilityRole="button" - accessibilityLabel={_(msg`Unfollow ${profile.handle}`)} - accessibilityHint={_( - msg`Hides posts from ${profile.handle} in your feed`, - )}> - <FontAwesomeIcon - icon="check" - style={[pal.text, s.mr5]} - size={14} - /> - <Text type="button" style={pal.text}> - <Trans>Following</Trans> - </Text> - </TouchableOpacity> - ) : ( - <TouchableOpacity - testID="followBtn" - onPress={onPressFollow} - style={[styles.btn, styles.mainBtn, palInverted.view]} - accessibilityRole="button" - accessibilityLabel={_(msg`Follow ${profile.handle}`)} - accessibilityHint={_( - msg`Shows posts from ${profile.handle} in your feed`, - )}> - <FontAwesomeIcon - icon="plus" - style={[palInverted.text, s.mr5]} - /> - <Text type="button" style={[palInverted.text, s.bold]}> - <Trans>Follow</Trans> - </Text> - </TouchableOpacity> - )} - </> - ) : null} - <ProfileMenu profile={profile} /> - </View> - <View pointerEvents="none"> - <Text - testID="profileHeaderDisplayName" - type="title-2xl" - style={[pal.text, styles.title]}> - {sanitizeDisplayName( - profile.displayName || sanitizeHandle(profile.handle), - moderation.profile, - )} - </Text> - </View> - <View style={styles.handleLine} pointerEvents="none"> - {profile.viewer?.followedBy && !blockHide ? ( - <View style={[styles.pill, pal.btn, s.mr5]}> - <Text type="xs" style={[pal.text]}> - <Trans>Follows you</Trans> - </Text> - </View> - ) : undefined} - <ThemedText - type={invalidHandle ? 'xs' : 'md'} - fg={invalidHandle ? 'error' : 'light'} - border={invalidHandle ? 'error' : undefined} - style={[ - invalidHandle ? styles.invalidHandle : undefined, - styles.handle, - ]}> - {invalidHandle ? _(msg`âš Invalid Handle`) : `@${profile.handle}`} - </ThemedText> - </View> - {!isPlaceholderProfile && !blockHide && ( - <> - <View style={styles.metricsLine} pointerEvents="box-none"> - <Link - testID="profileHeaderFollowersButton" - style={[s.flexRow, s.mr10]} - href={makeProfileLink(profile, 'followers')} - onPressOut={() => - track(`ProfileHeader:FollowersButtonClicked`, { - handle: profile.handle, - }) - } - asAnchor - accessibilityLabel={`${followers} ${pluralizedFollowers}`} - accessibilityHint={_(msg`Opens followers list`)}> - <Text type="md" style={[s.bold, pal.text]}> - {followers}{' '} - </Text> - <Text type="md" style={[pal.textLight]}> - {pluralizedFollowers} - </Text> - </Link> - <Link - testID="profileHeaderFollowsButton" - style={[s.flexRow, s.mr10]} - href={makeProfileLink(profile, 'follows')} - onPressOut={() => - track(`ProfileHeader:FollowsButtonClicked`, { - handle: profile.handle, - }) - } - asAnchor - accessibilityLabel={_(msg`${following} following`)} - accessibilityHint={_(msg`Opens following list`)}> - <Trans> - <Text type="md" style={[s.bold, pal.text]}> - {following}{' '} - </Text> - <Text type="md" style={[pal.textLight]}> - following - </Text> - </Trans> - </Link> - <Text type="md" style={[s.bold, pal.text]}> - {formatCount(profile.postsCount || 0)}{' '} - <Text type="md" style={[pal.textLight]}> - {pluralize(profile.postsCount || 0, 'post')} - </Text> - </Text> - </View> - {descriptionRT && !moderation.profile.blur ? ( - <View pointerEvents="auto" style={[styles.description]}> - <RichText - testID="profileHeaderDescription" - style={[a.text_md]} - numberOfLines={15} - value={descriptionRT} - /> - </View> - ) : undefined} - </> - )} - <ProfileHeaderAlerts moderation={moderation} /> - {isMe && ( - <LabelInfo details={{did: profile.did}} labels={profile.labels} /> - )} - </View> - - {showSuggestedFollows && ( - <ProfileHeaderSuggestedFollows - actorDid={profile.did} - requestDismiss={() => { - if (showSuggestedFollows) { - setShowSuggestedFollows(false) - } else { - track('ProfileHeader:SuggestedFollowsOpened') - setShowSuggestedFollows(true) - } - }} - /> - )} - - {!isDesktop && !hideBackButton && ( - <TouchableWithoutFeedback - testID="profileHeaderBackBtn" - onPress={onPressBack} - hitSlop={BACK_HITSLOP} - accessibilityRole="button" - accessibilityLabel={_(msg`Back`)} - accessibilityHint=""> - <View style={styles.backBtnWrapper}> - <BlurView style={styles.backBtn} blurType="dark"> - <FontAwesomeIcon size={18} icon="angle-left" style={s.white} /> - </BlurView> - </View> - </TouchableWithoutFeedback> - )} - <TouchableWithoutFeedback - testID="profileHeaderAviButton" - onPress={onPressAvi} - accessibilityRole="image" - accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)} - accessibilityHint=""> - <View - style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}> - <UserAvatar - size={80} - avatar={profile.avatar} - moderation={moderation.avatar} - /> - </View> - </TouchableWithoutFeedback> - <Prompt.Basic - control={unblockPromptControl} - title={_(msg`Unblock Account?`)} - description={_( - msg`The account will be able to interact with you after unblocking.`, - )} - onConfirm={unblockAccount} - confirmButtonCta={ - profile.viewer?.blocking ? _(msg`Unblock`) : _(msg`Block`) - } - confirmButtonColor="negative" - /> - </View> - ) -} -ProfileHeader = memo(ProfileHeader) -export {ProfileHeader} - -const styles = StyleSheet.create({ - banner: { - width: '100%', - height: 120, - }, - backBtnWrapper: { - position: 'absolute', - top: 10, - left: 10, - width: 30, - height: 30, - overflow: 'hidden', - borderRadius: 15, - // @ts-ignore web only - cursor: 'pointer', - }, - backBtn: { - width: 30, - height: 30, - borderRadius: 15, - alignItems: 'center', - justifyContent: 'center', - }, - avi: { - position: 'absolute', - top: 110, - left: 10, - width: 84, - height: 84, - borderRadius: 42, - borderWidth: 2, - }, - content: { - paddingTop: 8, - paddingHorizontal: 14, - paddingBottom: 4, - }, - - buttonsLine: { - flexDirection: 'row', - marginLeft: 'auto', - marginBottom: 12, - }, - primaryBtn: { - backgroundColor: colors.blue3, - paddingHorizontal: 24, - paddingVertical: 6, - }, - mainBtn: { - paddingHorizontal: 24, - }, - secondaryBtn: { - paddingHorizontal: 14, - }, - btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - paddingVertical: 7, - borderRadius: 50, - marginLeft: 6, - }, - title: {lineHeight: 38}, - - // Word wrapping appears fine on - // mobile but overflows on desktop - handle: isNative - ? {} - : { - // @ts-ignore web only -prf - wordBreak: 'break-all', - }, - invalidHandle: { - borderWidth: 1, - borderRadius: 4, - paddingHorizontal: 4, - }, - - handleLine: { - flexDirection: 'row', - marginBottom: 8, - }, - - metricsLine: { - flexDirection: 'row', - marginBottom: 8, - }, - - description: { - marginBottom: 8, - }, - - detailLine: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 5, - }, - - pill: { - borderRadius: 4, - paddingHorizontal: 6, - paddingVertical: 2, - }, - - br40: {borderRadius: 40}, - br50: {borderRadius: 50}, -}) diff --git a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx index 8f2c89499..3602cdb9a 100644 --- a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx +++ b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx @@ -21,7 +21,8 @@ import {useModerationOpts} from '#/state/queries/preferences' import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileFollowMutationQueue} from '#/state/queries/profile' -import {Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {Trans, msg} from '@lingui/macro' const OUTER_PADDING = 10 const INNER_PADDING = 14 @@ -98,9 +99,11 @@ export function ProfileHeaderSuggestedFollows({ <SuggestedFollowSkeleton /> </> ) : data ? ( - data.suggestions.map(profile => ( - <SuggestedFollow key={profile.did} profile={profile} /> - )) + data.suggestions + .filter(s => (s.associated?.labeler ? false : true)) + .map(profile => ( + <SuggestedFollow key={profile.did} profile={profile} /> + )) ) : ( <View /> )} @@ -168,6 +171,7 @@ function SuggestedFollow({ }) { const {track} = useAnalytics() const pal = usePalette('default') + const {_} = useLingui() const moderationOpts = useModerationOpts() const profile = useProfileShadow(profileUnshadowed) const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( @@ -181,20 +185,20 @@ function SuggestedFollow({ await queueFollow() } catch (e: any) { if (e?.name !== 'AbortError') { - Toast.show('An issue occurred, please try again.') + Toast.show(_(msg`An issue occurred, please try again.`)) } } - }, [queueFollow, track]) + }, [queueFollow, track, _]) const onPressUnfollow = React.useCallback(async () => { try { await queueUnfollow() } catch (e: any) { if (e?.name !== 'AbortError') { - Toast.show('An issue occurred, please try again.') + Toast.show(_(msg`An issue occurred, please try again.`)) } } - }, [queueUnfollow]) + }, [queueUnfollow, _]) if (!moderationOpts) { return null @@ -217,7 +221,7 @@ function SuggestedFollow({ <UserAvatar size={60} avatar={profile.avatar} - moderation={moderation.avatar} + moderation={moderation.ui('avatar')} /> <View style={{width: '100%', paddingVertical: 12}}> @@ -227,7 +231,7 @@ function SuggestedFollow({ numberOfLines={1}> {sanitizeDisplayName( profile.displayName || sanitizeHandle(profile.handle), - moderation.profile, + moderation.ui('displayName'), )} </Text> <Text @@ -239,7 +243,7 @@ function SuggestedFollow({ </View> <Button - label={following ? 'Unfollow' : 'Follow'} + label={following ? _(msg`Unfollow`) : _(msg`Follow`)} type="inverted" labelStyle={{textAlign: 'center'}} onPress={following ? onPressUnfollow : onPressFollow} diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx index 0baa4f394..cb0b1d97c 100644 --- a/src/view/com/profile/ProfileMenu.tsx +++ b/src/view/com/profile/ProfileMenu.tsx @@ -17,6 +17,7 @@ import {toShareUrl} from 'lib/strings/url-helpers' import {makeProfileLink} from 'lib/routes/links' import {useAnalytics} from 'lib/analytics/analytics' import {useModalControls} from 'state/modals' +import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' import { RQKEY as profileQueryKey, useProfileBlockMutationQueue, @@ -31,6 +32,7 @@ import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheck} from '#/components/icons/PersonCheck' import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/PersonX' import {PeopleRemove2_Stroke2_Corner0_Rounded as UserMinus} from '#/components/icons/PeopleRemove2' +import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {logger} from '#/logger' import {Shadow} from 'state/cache/types' import * as Prompt from '#/components/Prompt' @@ -47,12 +49,17 @@ let ProfileMenu = ({ const pal = usePalette('default') const {track} = useAnalytics() const {openModal} = useModalControls() + const reportDialogControl = useReportDialogControl() const queryClient = useQueryClient() const isSelf = currentAccount?.did === profile.did + const isFollowing = profile.viewer?.following + const isBlocked = profile.viewer?.blocking || profile.viewer?.blockedBy + const isFollowingBlockedAccount = isFollowing && isBlocked + const isLabelerAndNotBlocked = !!profile.associated?.labeler && !isBlocked const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile) const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) - const [, queueUnfollow] = useProfileFollowMutationQueue( + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( profile, 'ProfileMenu', ) @@ -139,6 +146,19 @@ let ProfileMenu = ({ } }, [profile.viewer?.blocking, track, _, queueUnblock, queueBlock]) + const onPressFollowAccount = React.useCallback(async () => { + track('ProfileHeader:FollowButtonClicked') + try { + await queueFollow() + Toast.show(_(msg`Account followed`)) + } catch (e: any) { + if (e?.name !== 'AbortError') { + logger.error('Failed to follow account', {message: e}) + Toast.show(_(msg`There was an issue! ${e.toString()}`)) + } + } + }, [_, queueFollow, track]) + const onPressUnfollowAccount = React.useCallback(async () => { track('ProfileHeader:UnfollowButtonClicked') try { @@ -154,11 +174,8 @@ let ProfileMenu = ({ const onPressReportAccount = React.useCallback(() => { track('ProfileHeader:ReportAccountButtonClicked') - openModal({ - name: 'report', - did: profile.did, - }) - }, [track, openModal, profile]) + reportDialogControl.open() + }, [track, reportDialogControl]) return ( <EventStopper onKeyDown={false}> @@ -175,10 +192,9 @@ let ProfileMenu = ({ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - paddingVertical: 7, + paddingVertical: 10, borderRadius: 50, - marginLeft: 6, - paddingHorizontal: 14, + paddingHorizontal: 16, }, pal.btn, ]}> @@ -210,10 +226,38 @@ let ProfileMenu = ({ <Menu.ItemIcon icon={Share} /> </Menu.Item> </Menu.Group> + {hasSession && ( <> <Menu.Divider /> <Menu.Group> + {!isSelf && ( + <> + {(isLabelerAndNotBlocked || isFollowingBlockedAccount) && ( + <Menu.Item + testID="profileHeaderDropdownFollowBtn" + label={ + isFollowing + ? _(msg`Unfollow Account`) + : _(msg`Follow Account`) + } + onPress={ + isFollowing + ? onPressUnfollowAccount + : onPressFollowAccount + }> + <Menu.ItemText> + {isFollowing ? ( + <Trans>Unfollow Account</Trans> + ) : ( + <Trans>Follow Account</Trans> + )} + </Menu.ItemText> + <Menu.ItemIcon icon={isFollowing ? UserMinus : Plus} /> + </Menu.Item> + )} + </> + )} <Menu.Item testID="profileHeaderDropdownListAddRemoveBtn" label={_(msg`Add to Lists`)} @@ -225,18 +269,6 @@ let ProfileMenu = ({ </Menu.Item> {!isSelf && ( <> - {profile.viewer?.following && - (profile.viewer.blocking || profile.viewer.blockedBy) && ( - <Menu.Item - testID="profileHeaderDropdownUnfollowBtn" - label={_(msg`Unfollow Account`)} - onPress={onPressUnfollowAccount}> - <Menu.ItemText> - <Trans>Unfollow Account</Trans> - </Menu.ItemText> - <Menu.ItemIcon icon={UserMinus} /> - </Menu.Item> - )} {!profile.viewer?.blocking && !profile.viewer?.mutedByList && ( <Menu.Item @@ -299,6 +331,11 @@ let ProfileMenu = ({ </Menu.Outer> </Menu.Root> + <ReportDialog + control={reportDialogControl} + params={{type: 'account', did: profile.did}} + /> + <Prompt.Basic control={blockPromptControl} title={ @@ -311,6 +348,10 @@ let ProfileMenu = ({ ? _( msg`The account will be able to interact with you after unblocking.`, ) + : profile.associated?.labeler + ? _( + msg`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.`, + ) : _( msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, ) diff --git a/src/view/com/util/BottomSheetCustomBackdrop.tsx b/src/view/com/util/BottomSheetCustomBackdrop.tsx index ed5a2f165..ab6570252 100644 --- a/src/view/com/util/BottomSheetCustomBackdrop.tsx +++ b/src/view/com/util/BottomSheetCustomBackdrop.tsx @@ -6,12 +6,15 @@ import Animated, { interpolate, useAnimatedStyle, } from 'react-native-reanimated' -import {t} from '@lingui/macro' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' export function createCustomBackdrop( onClose?: (() => void) | undefined, ): React.FC<BottomSheetBackdropProps> { const CustomBackdrop = ({animatedIndex, style}: BottomSheetBackdropProps) => { + const {_} = useLingui() + // animated variables const opacity = useAnimatedStyle(() => ({ opacity: interpolate( @@ -30,7 +33,7 @@ export function createCustomBackdrop( return ( <TouchableWithoutFeedback onPress={onClose} - accessibilityLabel={t`Close bottom drawer`} + accessibilityLabel={_(msg`Close bottom drawer`)} accessibilityHint="" onAccessibilityEscape={() => { if (onClose !== undefined) { diff --git a/src/view/com/util/ErrorBoundary.tsx b/src/view/com/util/ErrorBoundary.tsx index 5ec1d0014..22fdd606e 100644 --- a/src/view/com/util/ErrorBoundary.tsx +++ b/src/view/com/util/ErrorBoundary.tsx @@ -1,8 +1,9 @@ import React, {Component, ErrorInfo, ReactNode} from 'react' import {ErrorScreen} from './error/ErrorScreen' import {CenteredView} from './Views' -import {t} from '@lingui/macro' +import {msg} from '@lingui/macro' import {logger} from '#/logger' +import {useLingui} from '@lingui/react' interface Props { children?: ReactNode @@ -31,11 +32,7 @@ export class ErrorBoundary extends Component<Props, State> { if (this.state.hasError) { return ( <CenteredView style={{height: '100%', flex: 1}}> - <ErrorScreen - title={t`Oh no!`} - message={t`There was an unexpected issue in the application. Please let us know if this happened to you!`} - details={this.state.error.toString()} - /> + <TranslatedErrorScreen details={this.state.error.toString()} /> </CenteredView> ) } @@ -43,3 +40,17 @@ export class ErrorBoundary extends Component<Props, State> { return this.props.children } } + +function TranslatedErrorScreen({details}: {details?: string}) { + const {_} = useLingui() + + return ( + <ErrorScreen + title={_(msg`Oh no!`)} + message={_( + msg`There was an unexpected issue in the application. Please let us know if this happened to you!`, + )} + details={details} + /> + ) +} diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index 7468111b5..b6c512b09 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -47,6 +47,7 @@ interface Props extends ComponentProps<typeof TouchableOpacity> { anchorNoUnderline?: boolean navigationAction?: 'push' | 'replace' | 'navigate' onPointerEnter?: () => void + onBeforePress?: () => void } export const Link = memo(function Link({ @@ -60,6 +61,7 @@ export const Link = memo(function Link({ accessible, anchorNoUnderline, navigationAction, + onBeforePress, ...props }: Props) { const t = useTheme() @@ -70,6 +72,7 @@ export const Link = memo(function Link({ const onPress = React.useCallback( (e?: Event) => { + onBeforePress?.() if (typeof href === 'string') { return onPressInner( closeModal, @@ -81,7 +84,7 @@ export const Link = memo(function Link({ ) } }, - [closeModal, navigation, navigationAction, href, openLink], + [closeModal, navigation, navigationAction, href, openLink, onBeforePress], ) if (noFeedback) { @@ -262,6 +265,7 @@ interface TextLinkOnWebOnlyProps extends TextProps { accessibilityHint?: string title?: string navigationAction?: 'push' | 'replace' | 'navigate' + disableMismatchWarning?: boolean onPointerEnter?: () => void } export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({ @@ -273,6 +277,7 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({ numberOfLines, lineHeight, navigationAction, + disableMismatchWarning, ...props }: TextLinkOnWebOnlyProps) { if (isWeb) { @@ -287,6 +292,7 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({ lineHeight={lineHeight} title={props.title} navigationAction={navigationAction} + disableMismatchWarning={disableMismatchWarning} {...props} /> ) diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index 3795dcf13..529fc54e0 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -11,16 +11,12 @@ import {sanitizeHandle} from 'lib/strings/handles' import {isAndroid, isWeb} from 'platform/detection' import {TimeElapsed} from './TimeElapsed' import {makeProfileLink} from 'lib/routes/links' -import {ModerationUI} from '@atproto/api' +import {AppBskyActorDefs, ModerationDecision, ModerationUI} from '@atproto/api' import {usePrefetchProfileQuery} from '#/state/queries/profile' interface PostMetaOpts { - author: { - avatar?: string - did: string - handle: string - displayName?: string | undefined - } + author: AppBskyActorDefs.ProfileViewBasic + moderation: ModerationDecision | undefined authorHasWarning: boolean postHref: string timestamp: string @@ -46,6 +42,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { avatar={opts.author.avatar} size={opts.avatarSize || 16} moderation={opts.avatarModeration} + type={opts.author.associated?.labeler ? 'labeler' : 'user'} /> </View> )} @@ -55,9 +52,14 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { style={[pal.text, opts.displayNameStyle]} numberOfLines={1} lineHeight={1.2} + disableMismatchWarning text={ <> - {sanitizeDisplayName(displayName)} + {sanitizeDisplayName( + displayName, + opts.moderation?.ui('displayName'), + )} + <Text type="md" numberOfLines={1} diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index 413237397..8656c3f51 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -24,9 +24,9 @@ import { } from '#/components/icons/Camera' import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' -import {useTheme} from '#/alf' +import {useTheme, tokens} from '#/alf' -export type UserAvatarType = 'user' | 'algo' | 'list' +export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler' interface BaseUserAvatarProps { type?: UserAvatarType @@ -101,6 +101,33 @@ let DefaultAvatar = ({ </Svg> ) } + if (type === 'labeler') { + return ( + <Svg + testID="userAvatarFallback" + width={size} + height={size} + viewBox="0 0 32 32" + fill="none" + stroke="none"> + <Rect + x="0" + y="0" + width="32" + height="32" + rx="3" + fill={tokens.color.temp_purple} + /> + <Path + d="M24 9.75L16 7L8 9.75V15.9123C8 20.8848 12 23 16 25.1579C20 23 24 20.8848 24 15.9123V9.75Z" + stroke="white" + strokeWidth="2" + strokeLinecap="square" + strokeLinejoin="round" + /> + </Svg> + ) + } return ( <Svg testID="userAvatarFallback" @@ -134,7 +161,7 @@ let UserAvatar = ({ const backgroundColor = pal.colors.backgroundLight const aviStyle = useMemo(() => { - if (type === 'algo' || type === 'list') { + if (type === 'algo' || type === 'list' || type === 'labeler') { return { width: size, height: size, diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx index a5ddfee8a..4fb3726cd 100644 --- a/src/view/com/util/UserBanner.tsx +++ b/src/view/com/util/UserBanner.tsx @@ -7,7 +7,7 @@ import {msg, Trans} from '@lingui/macro' import {colors} from 'lib/styles' import {useTheme} from 'lib/ThemeContext' -import {useTheme as useAlfTheme} from '#/alf' +import {useTheme as useAlfTheme, tokens} from '#/alf' import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' import { usePhotoLibraryPermission, @@ -26,10 +26,12 @@ import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/ico import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' export function UserBanner({ + type, banner, moderation, onSelectNewBanner, }: { + type?: 'labeler' | 'default' banner?: string | null moderation?: ModerationUI onSelectNewBanner?: (img: RNImage | null) => void @@ -167,7 +169,10 @@ export function UserBanner({ ) : ( <View testID="userBannerFallback" - style={[styles.bannerImage, styles.defaultBanner]} + style={[ + styles.bannerImage, + type === 'labeler' ? styles.labelerBanner : styles.defaultBanner, + ]} /> ) } @@ -191,4 +196,7 @@ const styles = StyleSheet.create({ defaultBanner: { backgroundColor: '#0070ff', }, + labelerBanner: { + backgroundColor: tokens.color.temp_purple, + }, }) diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 8fc3d9ea6..70fbb907f 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -16,7 +16,6 @@ import * as Toast from '../Toast' import {EventStopper} from '../EventStopper' import {useDialogControl} from '#/components/Dialog' import * as Prompt from '#/components/Prompt' -import {useModalControls} from '#/state/modals' import {makeProfileLink} from '#/lib/routes/links' import {CommonNavigatorParams} from '#/lib/routes/types' import {getCurrentRoute} from 'lib/routes/helpers' @@ -33,6 +32,7 @@ import {useSession} from '#/state/session' import {isWeb} from '#/platform/detection' import {richTextToString} from '#/lib/strings/rich-text-helpers' import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' +import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' import {atoms as a, useTheme as useAlf} from '#/alf' import * as Menu from '#/components/Menu' @@ -45,7 +45,6 @@ import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/ import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' -import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' let PostDropdownBtn = ({ testID, @@ -55,7 +54,6 @@ let PostDropdownBtn = ({ record, richText, style, - showAppealLabelItem, hitSlop, }: { testID: string @@ -65,7 +63,6 @@ let PostDropdownBtn = ({ record: AppBskyFeedPost.Record richText: RichTextAPI style?: StyleProp<ViewStyle> - showAppealLabelItem?: boolean hitSlop?: PressableProps['hitSlop'] }): React.ReactNode => { const {hasSession, currentAccount} = useSession() @@ -73,7 +70,6 @@ let PostDropdownBtn = ({ const alf = useAlf() const {_} = useLingui() const defaultCtrlColor = theme.palette.default.postCtrl - const {openModal} = useModalControls() const langPrefs = useLanguagePrefs() const mutedThreads = useMutedThreads() const toggleThreadMute = useToggleThreadMute() @@ -83,6 +79,7 @@ let PostDropdownBtn = ({ const openLink = useOpenLink() const navigation = useNavigation() const {mutedWordsDialogControl} = useGlobalDialogsControlContext() + const reportDialogControl = useReportDialogControl() const deletePromptControl = useDialogControl() const hidePromptControl = useDialogControl() const loggedOutWarningPromptControl = useDialogControl() @@ -293,13 +290,7 @@ let PostDropdownBtn = ({ <Menu.Item testID="postDropdownReportBtn" label={_(msg`Report post`)} - onPress={() => { - openModal({ - name: 'report', - uri: postUri, - cid: postCid, - }) - }}> + onPress={() => reportDialogControl.open()}> <Menu.ItemText>{_(msg`Report post`)}</Menu.ItemText> <Menu.ItemIcon icon={Warning} position="right" /> </Menu.Item> @@ -314,28 +305,6 @@ let PostDropdownBtn = ({ <Menu.ItemIcon icon={Trash} position="right" /> </Menu.Item> )} - - {showAppealLabelItem && ( - <> - <Menu.Divider /> - - <Menu.Item - testID="postDropdownAppealBtn" - label={_(msg`Appeal content warning`)} - onPress={() => { - openModal({ - name: 'appeal-label', - uri: postUri, - cid: postCid, - }) - }}> - <Menu.ItemText> - {_(msg`Appeal content warning`)} - </Menu.ItemText> - <Menu.ItemIcon icon={CircleInfo} position="right" /> - </Menu.Item> - </> - )} </Menu.Group> </Menu.Outer> </Menu.Root> @@ -359,6 +328,15 @@ let PostDropdownBtn = ({ confirmButtonCta={_(msg`Hide`)} /> + <ReportDialog + control={reportDialogControl} + params={{ + type: 'post', + uri: postUri, + cid: postCid, + }} + /> + <Prompt.Basic control={loggedOutWarningPromptControl} title={_(msg`Note about sharing`)} diff --git a/src/view/com/util/moderation/ContentHider.tsx b/src/view/com/util/moderation/ContentHider.tsx deleted file mode 100644 index cd2545290..000000000 --- a/src/view/com/util/moderation/ContentHider.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React from 'react' -import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {usePalette} from 'lib/hooks/usePalette' -import {ModerationUI, PostModeration} from '@atproto/api' -import {Text} from '../text/Text' -import {ShieldExclamation} from 'lib/icons' -import {describeModerationCause} from 'lib/moderation' -import {useLingui} from '@lingui/react' -import {msg, Trans} from '@lingui/macro' -import {useModalControls} from '#/state/modals' -import {isPostMediaBlurred} from 'lib/moderation' - -export function ContentHider({ - testID, - moderation, - moderationDecisions, - ignoreMute, - ignoreQuoteDecisions, - style, - childContainerStyle, - children, -}: React.PropsWithChildren<{ - testID?: string - moderation: ModerationUI - moderationDecisions?: PostModeration['decisions'] - ignoreMute?: boolean - ignoreQuoteDecisions?: boolean - style?: StyleProp<ViewStyle> - childContainerStyle?: StyleProp<ViewStyle> -}>) { - const pal = usePalette('default') - const {_} = useLingui() - const [override, setOverride] = React.useState(false) - const {openModal} = useModalControls() - - if ( - !moderation.blur || - (ignoreMute && moderation.cause?.type === 'muted') || - shouldIgnoreQuote(moderationDecisions, ignoreQuoteDecisions) - ) { - return ( - <View testID={testID} style={[styles.outer, style]}> - {children} - </View> - ) - } - - const isMute = ['muted', 'muted-word'].includes(moderation.cause?.type || '') - const desc = describeModerationCause(moderation.cause, 'content') - return ( - <View testID={testID} style={[styles.outer, style]}> - <Pressable - onPress={() => { - if (!moderation.noOverride) { - setOverride(v => !v) - } else { - openModal({ - name: 'moderation-details', - context: 'content', - moderation, - }) - } - }} - accessibilityRole="button" - accessibilityHint={ - override ? _(msg`Hide the content`) : _(msg`Show the content`) - } - accessibilityLabel="" - style={[ - styles.cover, - moderation.noOverride - ? {borderWidth: 1, borderColor: pal.colors.borderDark} - : pal.viewLight, - ]}> - <Pressable - onPress={() => { - openModal({ - name: 'moderation-details', - context: 'content', - moderation, - }) - }} - accessibilityRole="button" - accessibilityLabel={_(msg`Learn more about this warning`)} - accessibilityHint=""> - {isMute ? ( - <FontAwesomeIcon - icon={['far', 'eye-slash']} - size={18} - color={pal.colors.textLight} - /> - ) : ( - <ShieldExclamation size={18} style={pal.textLight} /> - )} - </Pressable> - <Text type="md" style={[pal.text, {flex: 1}]} numberOfLines={2}> - {desc.name} - </Text> - <View style={styles.showBtn}> - <Text type="lg" style={pal.link}> - {moderation.noOverride ? ( - <Trans>Learn more</Trans> - ) : override ? ( - <Trans>Hide</Trans> - ) : ( - <Trans>Show</Trans> - )} - </Text> - </View> - </Pressable> - {override && <View style={childContainerStyle}>{children}</View>} - </View> - ) -} - -function shouldIgnoreQuote( - decisions: PostModeration['decisions'] | undefined, - ignore: boolean | undefined, -): boolean { - if (!decisions || !ignore) { - return false - } - return !isPostMediaBlurred(decisions) -} - -const styles = StyleSheet.create({ - outer: { - overflow: 'hidden', - }, - cover: { - flexDirection: 'row', - alignItems: 'center', - gap: 6, - borderRadius: 8, - marginTop: 4, - paddingVertical: 14, - paddingLeft: 14, - paddingRight: 18, - }, - showBtn: { - marginLeft: 'auto', - alignSelf: 'center', - }, -}) diff --git a/src/view/com/util/moderation/LabelInfo.tsx b/src/view/com/util/moderation/LabelInfo.tsx deleted file mode 100644 index 970338752..000000000 --- a/src/view/com/util/moderation/LabelInfo.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react' -import {Pressable, StyleProp, View, ViewStyle} from 'react-native' -import {ComAtprotoLabelDefs} from '@atproto/api' -import {Text} from '../text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' - -export function LabelInfo({ - details, - labels, - style, -}: { - details: {did: string} | {uri: string; cid: string} - labels: ComAtprotoLabelDefs.Label[] | undefined - style?: StyleProp<ViewStyle> -}) { - const pal = usePalette('default') - const {_} = useLingui() - const {openModal} = useModalControls() - - if (!labels) { - return null - } - labels = labels.filter(l => !l.val.startsWith('!')) - if (!labels.length) { - return null - } - - return ( - <View - style={[ - pal.viewLight, - { - flexDirection: 'row', - flexWrap: 'wrap', - paddingHorizontal: 12, - paddingVertical: 10, - borderRadius: 8, - }, - style, - ]}> - <Text type="sm" style={pal.text}> - <Trans> - A content warning has been applied to this{' '} - {'did' in details ? 'account' : 'post'}. - </Trans>{' '} - </Text> - <Pressable - accessibilityRole="button" - accessibilityLabel={_(msg`Appeal this decision`)} - accessibilityHint="" - onPress={() => openModal({name: 'appeal-label', ...details})}> - <Text type="sm" style={pal.link}> - <Trans>Appeal this decision.</Trans> - </Text> - </Pressable> - </View> - ) -} diff --git a/src/view/com/util/moderation/PostAlerts.tsx b/src/view/com/util/moderation/PostAlerts.tsx deleted file mode 100644 index bc5bf9b32..000000000 --- a/src/view/com/util/moderation/PostAlerts.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react' -import {Pressable, StyleProp, StyleSheet, ViewStyle} from 'react-native' -import {ModerationUI} from '@atproto/api' -import {Text} from '../text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {ShieldExclamation} from 'lib/icons' -import {describeModerationCause} from 'lib/moderation' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' - -export function PostAlerts({ - moderation, - style, -}: { - moderation: ModerationUI - includeMute?: boolean - style?: StyleProp<ViewStyle> -}) { - const pal = usePalette('default') - const {_} = useLingui() - const {openModal} = useModalControls() - - const shouldAlert = !!moderation.cause && moderation.alert - if (!shouldAlert) { - return null - } - - const desc = describeModerationCause(moderation.cause, 'content') - return ( - <Pressable - onPress={() => { - openModal({ - name: 'moderation-details', - context: 'content', - moderation, - }) - }} - accessibilityRole="button" - accessibilityLabel={_(msg`Learn more about this warning`)} - accessibilityHint="" - style={[styles.container, pal.viewLight, style]}> - <ShieldExclamation style={pal.text} size={16} /> - <Text type="lg" style={[pal.text]}> - {desc.name}{' '} - <Text type="lg" style={[pal.link, styles.learnMoreBtn]}> - <Trans>Learn More</Trans> - </Text> - </Text> - </Pressable> - ) -} - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - alignItems: 'center', - gap: 4, - paddingVertical: 8, - paddingLeft: 14, - paddingHorizontal: 16, - borderRadius: 8, - }, - learnMoreBtn: { - marginLeft: 'auto', - }, -}) diff --git a/src/view/com/util/moderation/ProfileHeaderAlerts.tsx b/src/view/com/util/moderation/ProfileHeaderAlerts.tsx deleted file mode 100644 index 0f07b679b..000000000 --- a/src/view/com/util/moderation/ProfileHeaderAlerts.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react' -import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native' -import {ProfileModeration} from '@atproto/api' -import {Text} from '../text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {ShieldExclamation} from 'lib/icons' -import { - describeModerationCause, - getProfileModerationCauses, -} from 'lib/moderation' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {useModalControls} from '#/state/modals' - -export function ProfileHeaderAlerts({ - moderation, - style, -}: { - moderation: ProfileModeration - style?: StyleProp<ViewStyle> -}) { - const pal = usePalette('default') - const {_} = useLingui() - const {openModal} = useModalControls() - - const causes = getProfileModerationCauses(moderation) - if (!causes.length) { - return null - } - - return ( - <View style={styles.grid}> - {causes.map(cause => { - const isMute = cause.type === 'muted' - const desc = describeModerationCause(cause, 'account') - return ( - <Pressable - testID="profileHeaderAlert" - key={desc.name} - onPress={() => { - openModal({ - name: 'moderation-details', - context: 'content', - moderation: {cause}, - }) - }} - accessibilityRole="button" - accessibilityLabel={_(msg`Learn more about this warning`)} - accessibilityHint="" - style={[styles.container, pal.viewLight, style]}> - {isMute ? ( - <FontAwesomeIcon - icon={['far', 'eye-slash']} - size={14} - color={pal.colors.textLight} - /> - ) : ( - <ShieldExclamation style={pal.text} size={18} /> - )} - <Text type="sm" style={[{flex: 1}, pal.text]}> - {desc.name} - </Text> - <Text type="sm" style={[pal.link, styles.learnMoreBtn]}> - <Trans>Learn More</Trans> - </Text> - </Pressable> - ) - })} - </View> - ) -} - -const styles = StyleSheet.create({ - grid: { - gap: 4, - }, - container: { - flexDirection: 'row', - alignItems: 'center', - gap: 8, - paddingVertical: 12, - paddingHorizontal: 16, - borderRadius: 8, - }, - learnMoreBtn: { - marginLeft: 'auto', - }, -}) diff --git a/src/view/com/util/moderation/ScreenHider.tsx b/src/view/com/util/moderation/ScreenHider.tsx deleted file mode 100644 index 86f0cbf7b..000000000 --- a/src/view/com/util/moderation/ScreenHider.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import React from 'react' -import { - TouchableWithoutFeedback, - StyleProp, - StyleSheet, - View, - ViewStyle, -} from 'react-native' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {useNavigation} from '@react-navigation/native' -import {ModerationUI} from '@atproto/api' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {NavigationProp} from 'lib/routes/types' -import {Text} from '../text/Text' -import {Button} from '../forms/Button' -import {describeModerationCause} from 'lib/moderation' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' -import {s} from '#/lib/styles' -import {CenteredView} from '../Views' - -export function ScreenHider({ - testID, - screenDescription, - moderation, - style, - containerStyle, - children, -}: React.PropsWithChildren<{ - testID?: string - screenDescription: string - moderation: ModerationUI - style?: StyleProp<ViewStyle> - containerStyle?: StyleProp<ViewStyle> -}>) { - const pal = usePalette('default') - const palInverted = usePalette('inverted') - const {_} = useLingui() - const [override, setOverride] = React.useState(false) - const navigation = useNavigation<NavigationProp>() - const {isMobile} = useWebMediaQueries() - const {openModal} = useModalControls() - - if (!moderation.blur || override) { - return ( - <View testID={testID} style={style}> - {children} - </View> - ) - } - - const isNoPwi = - moderation.cause?.type === 'label' && - moderation.cause?.labelDef.id === '!no-unauthenticated' - const desc = describeModerationCause(moderation.cause, 'account') - return ( - <CenteredView - style={[styles.container, pal.view, containerStyle]} - sideBorders> - <View style={styles.iconContainer}> - <View style={[styles.icon, palInverted.view]}> - <FontAwesomeIcon - icon={isNoPwi ? ['far', 'eye-slash'] : 'exclamation'} - style={pal.textInverted as FontAwesomeIconStyle} - size={24} - /> - </View> - </View> - <Text type="title-2xl" style={[styles.title, pal.text]}> - {isNoPwi ? ( - <Trans>Sign-in Required</Trans> - ) : ( - <Trans>Content Warning</Trans> - )} - </Text> - <Text type="2xl" style={[styles.description, pal.textLight]}> - {isNoPwi ? ( - <Trans> - This account has requested that users sign in to view their profile. - </Trans> - ) : ( - <> - <Trans>This {screenDescription} has been flagged:</Trans> - <Text type="2xl-medium" style={[pal.text, s.ml5]}> - {desc.name}. - </Text> - <TouchableWithoutFeedback - onPress={() => { - openModal({ - name: 'moderation-details', - context: 'account', - moderation, - }) - }} - accessibilityRole="button" - accessibilityLabel={_(msg`Learn more about this warning`)} - accessibilityHint=""> - <Text type="2xl" style={pal.link}> - <Trans>Learn More</Trans> - </Text> - </TouchableWithoutFeedback> - </> - )}{' '} - </Text> - {isMobile && <View style={styles.spacer} />} - <View style={styles.btnContainer}> - <Button - type="inverted" - onPress={() => { - if (navigation.canGoBack()) { - navigation.goBack() - } else { - navigation.navigate('Home') - } - }} - style={styles.btn}> - <Text type="button-lg" style={pal.textInverted}> - <Trans>Go back</Trans> - </Text> - </Button> - {!moderation.noOverride && ( - <Button - type="default" - onPress={() => setOverride(v => !v)} - style={styles.btn}> - <Text type="button-lg" style={pal.text}> - <Trans>Show anyway</Trans> - </Text> - </Button> - )} - </View> - </CenteredView> - ) -} - -const styles = StyleSheet.create({ - spacer: { - flex: 1, - }, - container: { - flex: 1, - paddingTop: 100, - paddingBottom: 150, - }, - iconContainer: { - alignItems: 'center', - marginBottom: 10, - }, - icon: { - borderRadius: 25, - width: 50, - height: 50, - alignItems: 'center', - justifyContent: 'center', - }, - title: { - textAlign: 'center', - marginBottom: 10, - }, - description: { - marginBottom: 10, - paddingHorizontal: 20, - textAlign: 'center', - }, - btnContainer: { - flexDirection: 'row', - justifyContent: 'center', - marginVertical: 10, - gap: 10, - }, - btn: { - paddingHorizontal: 20, - paddingVertical: 14, - }, -}) diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index c96954a11..3fa347a6d 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -41,7 +41,6 @@ let PostCtrls = ({ post, record, richText, - showAppealLabelItem, style, onPressReply, logContext, @@ -50,7 +49,6 @@ let PostCtrls = ({ post: Shadow<AppBskyFeedDefs.PostView> record: AppBskyFeedPost.Record richText: RichTextAPI - showAppealLabelItem?: boolean style?: StyleProp<ViewStyle> onPressReply: () => void logContext: 'FeedItem' | 'PostThreadItem' | 'Post' @@ -232,7 +230,6 @@ let PostCtrls = ({ postUri={post.uri} record={record} richText={richText} - showAppealLabelItem={showAppealLabelItem} style={styles.btnPad} hitSlop={big ? HITSLOP_20 : HITSLOP_10} /> diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index 35b091269..2b1c3e617 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -1,13 +1,15 @@ import React from 'react' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import { + AppBskyFeedDefs, AppBskyEmbedRecord, AppBskyFeedPost, AppBskyEmbedImages, AppBskyEmbedRecordWithMedia, - ModerationUI, AppBskyEmbedExternal, RichText as RichTextAPI, + moderatePost, + ModerationDecision, } from '@atproto/api' import {AtUri} from '@atproto/api' import {PostMeta} from '../PostMeta' @@ -16,20 +18,20 @@ import {Text} from '../text/Text' import {usePalette} from 'lib/hooks/usePalette' import {ComposerOptsQuote} from 'state/shell/composer' import {PostEmbeds} from '.' -import {PostAlerts} from '../moderation/PostAlerts' +import {PostAlerts} from '../../../../components/moderation/PostAlerts' import {makeProfileLink} from 'lib/routes/links' import {InfoCircleIcon} from 'lib/icons' import {Trans} from '@lingui/macro' +import {useModerationOpts} from '#/state/queries/preferences' +import {ContentHider} from '../../../../components/moderation/ContentHider' import {RichText} from '#/components/RichText' import {atoms as a} from '#/alf' export function MaybeQuoteEmbed({ embed, - moderation, style, }: { embed: AppBskyEmbedRecord.View - moderation: ModerationUI style?: StyleProp<ViewStyle> }) { const pal = usePalette('default') @@ -39,17 +41,9 @@ export function MaybeQuoteEmbed({ AppBskyFeedPost.validateRecord(embed.record.value).success ) { return ( - <QuoteEmbed - quote={{ - author: embed.record.author, - cid: embed.record.cid, - uri: embed.record.uri, - indexedAt: embed.record.indexedAt, - text: embed.record.value.text, - facets: embed.record.value.facets, - embeds: embed.record.embeds, - }} - moderation={moderation} + <QuoteEmbedModerated + viewRecord={embed.record} + postRecord={embed.record.value} style={style} /> ) @@ -75,19 +69,49 @@ export function MaybeQuoteEmbed({ return null } +function QuoteEmbedModerated({ + viewRecord, + postRecord, + style, +}: { + viewRecord: AppBskyEmbedRecord.ViewRecord + postRecord: AppBskyFeedPost.Record + style?: StyleProp<ViewStyle> +}) { + const moderationOpts = useModerationOpts() + const moderation = React.useMemo(() => { + return moderationOpts + ? moderatePost(viewRecordToPostView(viewRecord), moderationOpts) + : undefined + }, [viewRecord, moderationOpts]) + + const quote = { + author: viewRecord.author, + cid: viewRecord.cid, + uri: viewRecord.uri, + indexedAt: viewRecord.indexedAt, + text: postRecord.text, + facets: postRecord.facets, + embeds: viewRecord.embeds, + } + + return <QuoteEmbed quote={quote} moderation={moderation} style={style} /> +} + export function QuoteEmbed({ quote, moderation, style, }: { quote: ComposerOptsQuote - moderation?: ModerationUI + moderation?: ModerationDecision style?: StyleProp<ViewStyle> }) { const pal = usePalette('default') const itemUrip = new AtUri(quote.uri) const itemHref = makeProfileLink(quote.author, 'post', itemUrip.rkey) const itemTitle = `Post by ${quote.author.handle}` + const richText = React.useMemo( () => quote.text.trim() @@ -95,6 +119,7 @@ export function QuoteEmbed({ : undefined, [quote.text, quote.facets], ) + const embed = React.useMemo(() => { const e = quote.embeds?.[0] @@ -108,40 +133,52 @@ export function QuoteEmbed({ return e.media } }, [quote.embeds]) + return ( - <Link - style={[styles.container, pal.borderDark, style]} - hoverStyle={{borderColor: pal.colors.borderLinkHover}} - href={itemHref} - title={itemTitle}> - <View pointerEvents="none"> - <PostMeta - author={quote.author} - showAvatar - authorHasWarning={false} - postHref={itemHref} - timestamp={quote.indexedAt} - /> - </View> - {moderation ? ( - <PostAlerts moderation={moderation} style={styles.alert} /> - ) : null} - {richText ? ( - <RichText - enableTags - value={richText} - style={[a.text_md]} - numberOfLines={20} - disableLinks - authorHandle={quote.author.handle} - /> - ) : null} - {embed && <PostEmbeds embed={embed} moderation={{}} />} - </Link> + <ContentHider modui={moderation?.ui('contentList')}> + <Link + style={[styles.container, pal.borderDark, style]} + hoverStyle={{borderColor: pal.colors.borderLinkHover}} + href={itemHref} + title={itemTitle}> + <View pointerEvents="none"> + <PostMeta + author={quote.author} + moderation={moderation} + showAvatar + authorHasWarning={false} + postHref={itemHref} + timestamp={quote.indexedAt} + /> + </View> + {moderation ? ( + <PostAlerts modui={moderation.ui('contentView')} style={[a.py_xs]} /> + ) : null} + {richText ? ( + <RichText + value={richText} + style={[a.text_md]} + numberOfLines={20} + disableLinks + /> + ) : null} + {embed && <PostEmbeds embed={embed} moderation={moderation} />} + </Link> + </ContentHider> ) } -export default QuoteEmbed +function viewRecordToPostView( + viewRecord: AppBskyEmbedRecord.ViewRecord, +): AppBskyFeedDefs.PostView { + const {value, embeds, ...rest} = viewRecord + return { + ...rest, + $type: 'app.bsky.feed.defs#postView', + record: value, + embed: embeds?.[0], + } +} const styles = StyleSheet.create({ container: { diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index 7e235babb..47091fbb0 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -15,8 +15,7 @@ import { AppBskyEmbedRecordWithMedia, AppBskyFeedDefs, AppBskyGraphDefs, - ModerationUI, - PostModeration, + ModerationDecision, } from '@atproto/api' import {Link} from '../Link' import {ImageLayoutGrid} from '../images/ImageLayoutGrid' @@ -26,9 +25,8 @@ import {ExternalLinkEmbed} from './ExternalLinkEmbed' import {MaybeQuoteEmbed} from './QuoteEmbed' import {AutoSizedImage} from '../images/AutoSizedImage' import {ListEmbed} from './ListEmbed' -import {isCauseALabelOnUri, isQuoteBlurred} from 'lib/moderation' import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' -import {ContentHider} from '../moderation/ContentHider' +import {ContentHider} from '../../../../components/moderation/ContentHider' import {isNative} from '#/platform/detection' import {shareUrl} from '#/lib/sharing' @@ -42,12 +40,10 @@ type Embed = export function PostEmbeds({ embed, moderation, - moderationDecisions, style, }: { embed?: Embed - moderation: ModerationUI - moderationDecisions?: PostModeration['decisions'] + moderation?: ModerationDecision style?: StyleProp<ViewStyle> }) { const pal = usePalette('default') @@ -66,18 +62,10 @@ export function PostEmbeds({ // quote post with media // = if (AppBskyEmbedRecordWithMedia.isView(embed)) { - const isModOnQuote = - (AppBskyEmbedRecord.isViewRecord(embed.record.record) && - isCauseALabelOnUri(moderation.cause, embed.record.record.uri)) || - (moderationDecisions && isQuoteBlurred(moderationDecisions)) - const mediaModeration = isModOnQuote ? {} : moderation - const quoteModeration = isModOnQuote ? moderation : {} return ( <View style={style}> - <PostEmbeds embed={embed.media} moderation={mediaModeration} /> - <ContentHider moderation={quoteModeration}> - <MaybeQuoteEmbed embed={embed.record} moderation={quoteModeration} /> - </ContentHider> + <PostEmbeds embed={embed.media} moderation={moderation} /> + <MaybeQuoteEmbed embed={embed.record} /> </View> ) } @@ -86,6 +74,7 @@ export function PostEmbeds({ // custom feed embed (i.e. generator view) // = if (AppBskyFeedDefs.isGeneratorView(embed.record)) { + // TODO moderation return ( <FeedSourceCard feedUri={embed.record.uri} @@ -97,16 +86,13 @@ export function PostEmbeds({ // list embed if (AppBskyGraphDefs.isListView(embed.record)) { + // TODO moderation return <ListEmbed item={embed.record} /> } // quote post // = - return ( - <ContentHider moderation={moderation}> - <MaybeQuoteEmbed embed={embed} style={style} moderation={moderation} /> - </ContentHider> - ) + return <MaybeQuoteEmbed embed={embed} style={style} /> } // image embed @@ -132,35 +118,41 @@ export function PostEmbeds({ if (images.length === 1) { const {alt, thumb, aspectRatio} = images[0] return ( - <View style={[styles.imagesContainer, style]}> - <AutoSizedImage - alt={alt} - uri={thumb} - dimensionsHint={aspectRatio} - onPress={() => _openLightbox(0)} - onPressIn={() => onPressIn(0)} - style={[styles.singleImage]}> - {alt === '' ? null : ( - <View style={styles.altContainer}> - <Text style={styles.alt} accessible={false}> - ALT - </Text> - </View> - )} - </AutoSizedImage> - </View> + <ContentHider modui={moderation?.ui('contentMedia')}> + <View style={[styles.imagesContainer, style]}> + <AutoSizedImage + alt={alt} + uri={thumb} + dimensionsHint={aspectRatio} + onPress={() => _openLightbox(0)} + onPressIn={() => onPressIn(0)} + style={[styles.singleImage]}> + {alt === '' ? null : ( + <View style={styles.altContainer}> + <Text style={styles.alt} accessible={false}> + ALT + </Text> + </View> + )} + </AutoSizedImage> + </View> + </ContentHider> ) } return ( - <View style={[styles.imagesContainer, style]}> - <ImageLayoutGrid - images={embed.images} - onPress={_openLightbox} - onPressIn={onPressIn} - style={embed.images.length === 1 ? [styles.singleImage] : undefined} - /> - </View> + <ContentHider modui={moderation?.ui('contentMedia')}> + <View style={[styles.imagesContainer, style]}> + <ImageLayoutGrid + images={embed.images} + onPress={_openLightbox} + onPressIn={onPressIn} + style={ + embed.images.length === 1 ? [styles.singleImage] : undefined + } + /> + </View> + </ContentHider> ) } } @@ -171,15 +163,17 @@ export function PostEmbeds({ const link = embed.external return ( - <Link - asAnchor - anchorNoUnderline - href={link.uri} - style={[styles.extOuter, pal.view, pal.borderDark, style]} - hoverStyle={{borderColor: pal.colors.borderLinkHover}} - onLongPress={onShareExternal}> - <ExternalLinkEmbed link={link} /> - </Link> + <ContentHider modui={moderation?.ui('contentMedia')}> + <Link + asAnchor + anchorNoUnderline + href={link.uri} + style={[styles.extOuter, pal.view, pal.borderDark, style]} + hoverStyle={{borderColor: pal.colors.borderLinkHover}} + onLongPress={onShareExternal}> + <ExternalLinkEmbed link={link} /> + </Link> + </ContentHider> ) } diff --git a/src/view/screens/DebugMod.tsx b/src/view/screens/DebugMod.tsx new file mode 100644 index 000000000..64f2376a4 --- /dev/null +++ b/src/view/screens/DebugMod.tsx @@ -0,0 +1,923 @@ +import React from 'react' +import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' +import {View} from 'react-native' +import { + LABELS, + mock, + moderatePost, + moderateProfile, + ModerationOpts, + AppBskyActorDefs, + AppBskyFeedDefs, + AppBskyFeedPost, + LabelPreference, + ModerationDecision, + ModerationBehavior, + RichText, + ComAtprotoLabelDefs, + interpretLabelValueDefinition, +} from '@atproto/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {moderationOptsOverrideContext} from '#/state/queries/preferences' +import {useSession} from '#/state/session' +import {FeedNotification} from '#/state/queries/notifications/types' +import { + groupNotifications, + shouldFilterNotif, +} from '#/state/queries/notifications/util' + +import {atoms as a, useTheme} from '#/alf' +import {CenteredView, ScrollView} from '#/view/com/util/Views' +import {H1, H3, P, Text} from '#/components/Typography' +import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' +import * as Toggle from '#/components/forms/Toggle' +import * as ToggleButton from '#/components/forms/ToggleButton' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' +import { + ChevronBottom_Stroke2_Corner0_Rounded as ChevronBottom, + ChevronTop_Stroke2_Corner0_Rounded as ChevronTop, +} from '#/components/icons/Chevron' +import {ScreenHider} from '../../components/moderation/ScreenHider' +import {ProfileHeaderStandard} from '#/screens/Profile/Header/ProfileHeaderStandard' +import {ProfileCard} from '../com/profile/ProfileCard' +import {FeedItem} from '../com/posts/FeedItem' +import {FeedItem as NotifFeedItem} from '../com/notifications/FeedItem' +import {PostThreadItem} from '../com/post-thread/PostThreadItem' +import {Divider} from '#/components/Divider' + +const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys( + LABELS, +) as (keyof typeof LABELS)[] + +export const DebugModScreen = ({}: NativeStackScreenProps< + CommonNavigatorParams, + 'DebugMod' +>) => { + const t = useTheme() + const [scenario, setScenario] = React.useState<string[]>(['label']) + const [scenarioSwitches, setScenarioSwitches] = React.useState<string[]>([]) + const [label, setLabel] = React.useState<string[]>([LABEL_VALUES[0]]) + const [target, setTarget] = React.useState<string[]>(['account']) + const [visibility, setVisiblity] = React.useState<string[]>(['warn']) + const [customLabelDef, setCustomLabelDef] = + React.useState<ComAtprotoLabelDefs.LabelValueDefinition>({ + identifier: 'custom', + blurs: 'content', + severity: 'alert', + defaultSetting: 'warn', + locales: [ + { + lang: 'en', + name: 'Custom label', + description: 'A custom label created in this test environment', + }, + ], + }) + const [view, setView] = React.useState<string[]>(['post']) + const labelStrings = useGlobalLabelStrings() + const {currentAccount} = useSession() + + const isTargetMe = + scenario[0] === 'label' && scenarioSwitches.includes('targetMe') + const isSelfLabel = + scenario[0] === 'label' && scenarioSwitches.includes('selfLabel') + const noAdult = + scenario[0] === 'label' && scenarioSwitches.includes('noAdult') + const isLoggedOut = + scenario[0] === 'label' && scenarioSwitches.includes('loggedOut') + const isFollowing = scenarioSwitches.includes('following') + + const did = + isTargetMe && currentAccount ? currentAccount.did : 'did:web:bob.test' + + const profile = React.useMemo(() => { + const mockedProfile = mock.profileViewBasic({ + handle: `bob.test`, + displayName: 'Bob Robertson', + description: 'User with this as their bio', + labels: + scenario[0] === 'label' && target[0] === 'account' + ? [ + mock.label({ + src: isSelfLabel ? did : undefined, + val: label[0], + uri: `at://${did}/`, + }), + ] + : scenario[0] === 'label' && target[0] === 'profile' + ? [ + mock.label({ + src: isSelfLabel ? did : undefined, + val: label[0], + uri: `at://${did}/app.bsky.actor.profile/self`, + }), + ] + : undefined, + viewer: mock.actorViewerState({ + following: isFollowing + ? `at://${currentAccount?.did || ''}/app.bsky.graph.follow/1234` + : undefined, + muted: scenario[0] === 'mute', + mutedByList: undefined, + blockedBy: undefined, + blocking: + scenario[0] === 'block' + ? `at://did:web:alice.test/app.bsky.actor.block/fake` + : undefined, + blockingByList: undefined, + }), + }) + mockedProfile.did = did + mockedProfile.avatar = 'https://bsky.social/about/images/favicon-32x32.png' + mockedProfile.banner = + 'https://bsky.social/about/images/social-card-default-gradient.png' + return mockedProfile + }, [scenario, target, label, isSelfLabel, did, isFollowing, currentAccount]) + + const post = React.useMemo(() => { + return mock.postView({ + record: mock.post({ + text: "This is the body of the post. It's where the text goes. You get the idea.", + }), + author: profile, + labels: + scenario[0] === 'label' && target[0] === 'post' + ? [ + mock.label({ + src: isSelfLabel ? did : undefined, + val: label[0], + uri: `at://${did}/app.bsky.feed.post/fake`, + }), + ] + : undefined, + embed: + target[0] === 'embed' + ? mock.embedRecordView({ + record: mock.post({ + text: 'Embed', + }), + labels: + scenario[0] === 'label' && target[0] === 'embed' + ? [ + mock.label({ + src: isSelfLabel ? did : undefined, + val: label[0], + uri: `at://${did}/app.bsky.feed.post/fake`, + }), + ] + : undefined, + author: profile, + }) + : { + $type: 'app.bsky.embed.images#view', + images: [ + { + thumb: + 'https://bsky.social/about/images/social-card-default-gradient.png', + fullsize: + 'https://bsky.social/about/images/social-card-default-gradient.png', + alt: '', + }, + ], + }, + }) + }, [scenario, label, target, profile, isSelfLabel, did]) + + const replyNotif = React.useMemo(() => { + const notif = mock.replyNotification({ + record: mock.post({ + text: "This is the body of the post. It's where the text goes. You get the idea.", + reply: { + parent: { + uri: `at://${did}/app.bsky.feed.post/fake-parent`, + cid: 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq', + }, + root: { + uri: `at://${did}/app.bsky.feed.post/fake-parent`, + cid: 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq', + }, + }, + }), + author: profile, + labels: + scenario[0] === 'label' && target[0] === 'post' + ? [ + mock.label({ + src: isSelfLabel ? did : undefined, + val: label[0], + uri: `at://${did}/app.bsky.feed.post/fake`, + }), + ] + : undefined, + }) + const [item] = groupNotifications([notif]) + item.subject = mock.postView({ + record: notif.record as AppBskyFeedPost.Record, + author: profile, + labels: notif.labels, + }) + return item + }, [scenario, label, target, profile, isSelfLabel, did]) + + const followNotif = React.useMemo(() => { + const notif = mock.followNotification({ + author: profile, + subjectDid: currentAccount?.did || '', + }) + const [item] = groupNotifications([notif]) + return item + }, [profile, currentAccount]) + + const modOpts = React.useMemo(() => { + return { + userDid: isLoggedOut ? '' : isTargetMe ? did : 'did:web:alice.test', + prefs: { + adultContentEnabled: !noAdult, + labels: { + [label[0]]: visibility[0] as LabelPreference, + }, + labelers: [ + { + did: 'did:plc:fake-labeler', + labels: {[label[0]]: visibility[0] as LabelPreference}, + }, + ], + mutedWords: [], + hiddenPosts: [], + }, + labelDefs: { + 'did:plc:fake-labeler': [ + interpretLabelValueDefinition(customLabelDef, 'did:plc:fake-labeler'), + ], + }, + } + }, [label, visibility, noAdult, isLoggedOut, isTargetMe, did, customLabelDef]) + + const profileModeration = React.useMemo(() => { + return moderateProfile(profile, modOpts) + }, [profile, modOpts]) + const postModeration = React.useMemo(() => { + return moderatePost(post, modOpts) + }, [post, modOpts]) + + return ( + <moderationOptsOverrideContext.Provider value={modOpts}> + <ScrollView> + <CenteredView style={[t.atoms.bg, a.px_lg, a.py_lg]}> + <H1 style={[a.text_5xl, a.font_bold, a.pb_lg]}>Moderation states</H1> + + <Heading title="" subtitle="Scenario" /> + <ToggleButton.Group + label="Scenario" + values={scenario} + onChange={setScenario}> + <ToggleButton.Button name="label" label="Label"> + Label + </ToggleButton.Button> + <ToggleButton.Button name="block" label="Block"> + Block + </ToggleButton.Button> + <ToggleButton.Button name="mute" label="Mute"> + Mute + </ToggleButton.Button> + </ToggleButton.Group> + + {scenario[0] === 'label' && ( + <> + <View + style={[ + a.border, + a.rounded_sm, + a.mt_lg, + a.mb_lg, + a.p_lg, + t.atoms.border_contrast_medium, + ]}> + <Toggle.Group + label="Toggle" + type="radio" + values={label} + onChange={setLabel}> + <View style={[a.flex_row, a.gap_md, a.flex_wrap]}> + {LABEL_VALUES.map(labelValue => { + let targetFixed = target[0] + if ( + targetFixed !== 'account' && + targetFixed !== 'profile' + ) { + targetFixed = 'content' + } + const disabled = + isSelfLabel && + LABELS[labelValue].flags.includes('no-self') + return ( + <Toggle.Item + key={labelValue} + name={labelValue} + label={labelStrings[labelValue].name} + disabled={disabled} + style={disabled ? {opacity: 0.5} : undefined}> + <Toggle.Radio /> + <Toggle.Label>{labelValue}</Toggle.Label> + </Toggle.Item> + ) + })} + <Toggle.Item + name="custom" + label="Custom label" + disabled={isSelfLabel} + style={isSelfLabel ? {opacity: 0.5} : undefined}> + <Toggle.Radio /> + <Toggle.Label>Custom label</Toggle.Label> + </Toggle.Item> + </View> + </Toggle.Group> + + {label[0] === 'custom' ? ( + <CustomLabelForm + def={customLabelDef} + setDef={setCustomLabelDef} + /> + ) : ( + <> + <View style={{height: 10}} /> + <Divider /> + </> + )} + + <View style={{height: 10}} /> + + <SmallToggler label="Advanced"> + <Toggle.Group + label="Toggle" + type="checkbox" + values={scenarioSwitches} + onChange={setScenarioSwitches}> + <View style={[a.gap_md, a.flex_row, a.flex_wrap, a.pt_md]}> + <Toggle.Item name="targetMe" label="Target is me"> + <Toggle.Checkbox /> + <Toggle.Label>Target is me</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="following" label="Following target"> + <Toggle.Checkbox /> + <Toggle.Label>Following target</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="selfLabel" label="Self label"> + <Toggle.Checkbox /> + <Toggle.Label>Self label</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="noAdult" label="Adult disabled"> + <Toggle.Checkbox /> + <Toggle.Label>Adult disabled</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="loggedOut" label="Logged out"> + <Toggle.Checkbox /> + <Toggle.Label>Logged out</Toggle.Label> + </Toggle.Item> + </View> + </Toggle.Group> + + {LABELS[label[0] as keyof typeof LABELS]?.configurable !== + false && ( + <View style={[a.mt_md]}> + <Text + style={[a.font_bold, a.text_xs, t.atoms.text, a.pb_sm]}> + Preference + </Text> + <Toggle.Group + label="Preference" + type="radio" + values={visibility} + onChange={setVisiblity}> + <View + style={[ + a.flex_row, + a.gap_md, + a.flex_wrap, + a.align_center, + ]}> + <Toggle.Item name="hide" label="Hide"> + <Toggle.Radio /> + <Toggle.Label>Hide</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="warn" label="Warn"> + <Toggle.Radio /> + <Toggle.Label>Warn</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="ignore" label="Ignore"> + <Toggle.Radio /> + <Toggle.Label>Ignore</Toggle.Label> + </Toggle.Item> + </View> + </Toggle.Group> + </View> + )} + </SmallToggler> + </View> + + <View style={[a.flex_row, a.flex_wrap, a.gap_md]}> + <View> + <Text + style={[ + a.font_bold, + a.text_xs, + t.atoms.text, + a.pl_md, + a.pb_xs, + ]}> + Target + </Text> + <View + style={[ + a.border, + a.rounded_full, + a.px_md, + a.py_sm, + t.atoms.border_contrast_medium, + t.atoms.bg, + ]}> + <Toggle.Group + label="Target" + type="radio" + values={target} + onChange={setTarget}> + <View style={[a.flex_row, a.gap_md, a.flex_wrap]}> + <Toggle.Item name="account" label="Account"> + <Toggle.Radio /> + <Toggle.Label>Account</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="profile" label="Profile"> + <Toggle.Radio /> + <Toggle.Label>Profile</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="post" label="Post"> + <Toggle.Radio /> + <Toggle.Label>Post</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="embed" label="Embed"> + <Toggle.Radio /> + <Toggle.Label>Embed</Toggle.Label> + </Toggle.Item> + </View> + </Toggle.Group> + </View> + </View> + </View> + </> + )} + + <Spacer /> + + <Heading title="" subtitle="Results" /> + + <ToggleButton.Group label="Results" values={view} onChange={setView}> + <ToggleButton.Button name="post" label="Post"> + Post + </ToggleButton.Button> + <ToggleButton.Button name="notifications" label="Notifications"> + Notifications + </ToggleButton.Button> + <ToggleButton.Button name="account" label="Account"> + Account + </ToggleButton.Button> + <ToggleButton.Button name="data" label="Data"> + Data + </ToggleButton.Button> + </ToggleButton.Group> + + <View + style={[ + a.border, + a.rounded_sm, + a.mt_lg, + a.p_md, + t.atoms.border_contrast_medium, + ]}> + {view[0] === 'post' && ( + <> + <Heading title="Post" subtitle="in feed" /> + <MockPostFeedItem post={post} moderation={postModeration} /> + + <Heading title="Post" subtitle="viewed directly" /> + <MockPostThreadItem post={post} moderation={postModeration} /> + + <Heading title="Post" subtitle="reply in thread" /> + <MockPostThreadItem + post={post} + moderation={postModeration} + reply + /> + </> + )} + + {view[0] === 'notifications' && ( + <> + <Heading title="Notification" subtitle="quote or reply" /> + <MockNotifItem notif={replyNotif} moderationOpts={modOpts} /> + <View style={{height: 20}} /> + <Heading title="Notification" subtitle="follow or like" /> + <MockNotifItem notif={followNotif} moderationOpts={modOpts} /> + </> + )} + + {view[0] === 'account' && ( + <> + <Heading title="Account" subtitle="in listing" /> + <MockAccountCard + profile={profile} + moderation={profileModeration} + /> + + <Heading title="Account" subtitle="viewing directly" /> + <MockAccountScreen + profile={profile} + moderation={profileModeration} + moderationOpts={modOpts} + /> + </> + )} + + {view[0] === 'data' && ( + <> + <ModerationUIView + label="Profile Moderation UI" + mod={profileModeration} + /> + <ModerationUIView + label="Post Moderation UI" + mod={postModeration} + /> + <DataView + label={label[0]} + data={LABELS[label[0] as keyof typeof LABELS]} + /> + <DataView + label="Profile Moderation Data" + data={profileModeration} + /> + <DataView label="Post Moderation Data" data={postModeration} /> + </> + )} + </View> + + <View style={{height: 400}} /> + </CenteredView> + </ScrollView> + </moderationOptsOverrideContext.Provider> + ) +} + +function Heading({title, subtitle}: {title: string; subtitle?: string}) { + const t = useTheme() + return ( + <H3 style={[a.text_3xl, a.font_bold, a.pb_md]}> + {title}{' '} + {!!subtitle && ( + <H3 style={[t.atoms.text_contrast_medium, a.text_lg]}>{subtitle}</H3> + )} + </H3> + ) +} + +function CustomLabelForm({ + def, + setDef, +}: { + def: ComAtprotoLabelDefs.LabelValueDefinition + setDef: React.Dispatch< + React.SetStateAction<ComAtprotoLabelDefs.LabelValueDefinition> + > +}) { + const t = useTheme() + return ( + <View + style={[ + a.flex_row, + a.flex_wrap, + a.gap_md, + t.atoms.bg_contrast_25, + a.rounded_md, + a.p_md, + a.mt_md, + ]}> + <View> + <Text style={[a.font_bold, a.text_xs, t.atoms.text, a.pl_md, a.pb_xs]}> + Blurs + </Text> + <View + style={[ + a.border, + a.rounded_full, + a.px_md, + a.py_sm, + t.atoms.border_contrast_medium, + t.atoms.bg, + ]}> + <Toggle.Group + label="Blurs" + type="radio" + values={[def.blurs]} + onChange={values => setDef(v => ({...v, blurs: values[0]}))}> + <View style={[a.flex_row, a.gap_md, a.flex_wrap]}> + <Toggle.Item name="content" label="Content"> + <Toggle.Radio /> + <Toggle.Label>Content</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="media" label="Media"> + <Toggle.Radio /> + <Toggle.Label>Media</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="none" label="None"> + <Toggle.Radio /> + <Toggle.Label>None</Toggle.Label> + </Toggle.Item> + </View> + </Toggle.Group> + </View> + </View> + <View> + <Text style={[a.font_bold, a.text_xs, t.atoms.text, a.pl_md, a.pb_xs]}> + Severity + </Text> + <View + style={[ + a.border, + a.rounded_full, + a.px_md, + a.py_sm, + t.atoms.border_contrast_medium, + t.atoms.bg, + ]}> + <Toggle.Group + label="Severity" + type="radio" + values={[def.severity]} + onChange={values => setDef(v => ({...v, severity: values[0]}))}> + <View style={[a.flex_row, a.gap_md, a.flex_wrap, a.align_center]}> + <Toggle.Item name="alert" label="Alert"> + <Toggle.Radio /> + <Toggle.Label>Alert</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="inform" label="Inform"> + <Toggle.Radio /> + <Toggle.Label>Inform</Toggle.Label> + </Toggle.Item> + <Toggle.Item name="none" label="None"> + <Toggle.Radio /> + <Toggle.Label>None</Toggle.Label> + </Toggle.Item> + </View> + </Toggle.Group> + </View> + </View> + </View> + ) +} + +function Toggler({label, children}: React.PropsWithChildren<{label: string}>) { + const t = useTheme() + const [show, setShow] = React.useState(false) + return ( + <View style={a.mb_md}> + <View + style={[ + t.atoms.border_contrast_medium, + a.border, + a.rounded_sm, + a.p_xs, + ]}> + <Button + variant="solid" + color="secondary" + label="Toggle visibility" + size="small" + onPress={() => setShow(!show)}> + <ButtonText>{label}</ButtonText> + <ButtonIcon + icon={show ? ChevronTop : ChevronBottom} + position="right" + /> + </Button> + {show && children} + </View> + </View> + ) +} + +function SmallToggler({ + label, + children, +}: React.PropsWithChildren<{label: string}>) { + const [show, setShow] = React.useState(false) + return ( + <View> + <View style={[a.flex_row]}> + <Button + variant="ghost" + color="secondary" + label="Toggle visibility" + size="tiny" + onPress={() => setShow(!show)}> + <ButtonText>{label}</ButtonText> + <ButtonIcon + icon={show ? ChevronTop : ChevronBottom} + position="right" + /> + </Button> + </View> + {show && children} + </View> + ) +} + +function DataView({label, data}: {label: string; data: any}) { + return ( + <Toggler label={label}> + <Text style={[{fontFamily: 'monospace'}, a.p_md]}> + {JSON.stringify(data, null, 2)} + </Text> + </Toggler> + ) +} + +function ModerationUIView({ + mod, + label, +}: { + mod: ModerationDecision + label: string +}) { + return ( + <Toggler label={label}> + <View style={a.p_lg}> + {[ + 'profileList', + 'profileView', + 'avatar', + 'banner', + 'displayName', + 'contentList', + 'contentView', + 'contentMedia', + ].map(key => { + const ui = mod.ui(key as keyof ModerationBehavior) + return ( + <View key={key} style={[a.flex_row, a.gap_md]}> + <Text style={[a.font_bold, {width: 100}]}>{key}</Text> + <Flag v={ui.filter} label="Filter" /> + <Flag v={ui.blur} label="Blur" /> + <Flag v={ui.alert} label="Alert" /> + <Flag v={ui.inform} label="Inform" /> + <Flag v={ui.noOverride} label="No-override" /> + </View> + ) + })} + </View> + </Toggler> + ) +} + +function Spacer() { + return <View style={{height: 30}} /> +} + +function MockPostFeedItem({ + post, + moderation, +}: { + post: AppBskyFeedDefs.PostView + moderation: ModerationDecision +}) { + const t = useTheme() + if (moderation.ui('contentList').filter) { + return ( + <P style={[t.atoms.bg_contrast_25, a.px_lg, a.py_md, a.mb_lg]}> + Filtered from the feed + </P> + ) + } + return ( + <FeedItem + post={post} + record={post.record as AppBskyFeedPost.Record} + moderation={moderation} + reason={undefined} + /> + ) +} + +function MockPostThreadItem({ + post, + reply, +}: { + post: AppBskyFeedDefs.PostView + moderation: ModerationDecision + reply?: boolean +}) { + return ( + <PostThreadItem + // @ts-ignore + post={post} + record={post.record as AppBskyFeedPost.Record} + depth={reply ? 1 : 0} + isHighlightedPost={!reply} + treeView={false} + prevPost={undefined} + nextPost={undefined} + hasPrecedingItem={false} + onPostReply={() => {}} + /> + ) +} + +function MockNotifItem({ + notif, + moderationOpts, +}: { + notif: FeedNotification + moderationOpts: ModerationOpts +}) { + const t = useTheme() + if (shouldFilterNotif(notif.notification, moderationOpts)) { + return ( + <P style={[t.atoms.bg_contrast_25, a.px_lg, a.py_md]}> + Filtered from the feed + </P> + ) + } + return <NotifFeedItem item={notif} moderationOpts={moderationOpts} /> +} + +function MockAccountCard({ + profile, + moderation, +}: { + profile: AppBskyActorDefs.ProfileViewBasic + moderation: ModerationDecision +}) { + const t = useTheme() + + if (moderation.ui('profileList').filter) { + return ( + <P style={[t.atoms.bg_contrast_25, a.px_lg, a.py_md, a.mb_lg]}> + Filtered from the listing + </P> + ) + } + + return <ProfileCard profile={profile} /> +} + +function MockAccountScreen({ + profile, + moderation, + moderationOpts, +}: { + profile: AppBskyActorDefs.ProfileViewBasic + moderation: ModerationDecision + moderationOpts: ModerationOpts +}) { + const t = useTheme() + const {_} = useLingui() + return ( + <View style={[t.atoms.border_contrast_medium, a.border, a.mb_md]}> + <ScreenHider + style={{}} + screenDescription={_(msg`profile`)} + modui={moderation.ui('profileView')}> + <ProfileHeaderStandard + // @ts-ignore ProfileViewBasic is close enough -prf + profile={profile} + moderationOpts={moderationOpts} + descriptionRT={new RichText({text: profile.description as string})} + /> + </ScreenHider> + </View> + ) +} + +function Flag({v, label}: {v: boolean | undefined; label: string}) { + const t = useTheme() + return ( + <View style={[a.flex_row, a.align_center, a.gap_xs]}> + <View + style={[ + a.justify_center, + a.align_center, + a.rounded_xs, + a.border, + t.atoms.border_contrast_medium, + { + backgroundColor: t.palette.contrast_25, + width: 14, + height: 14, + }, + ]}> + {v && <Check size="xs" fill={t.palette.contrast_900} />} + </View> + <P style={a.text_xs}>{label}</P> + </View> + ) +} diff --git a/src/view/screens/LanguageSettings.tsx b/src/view/screens/LanguageSettings.tsx index 819840a46..b86cd46e1 100644 --- a/src/view/screens/LanguageSettings.tsx +++ b/src/view/screens/LanguageSettings.tsx @@ -97,7 +97,7 @@ export function LanguageSettingsScreen(_props: Props) { <Text style={[pal.text, s.pb10]}> <Trans> Select your app language for the default text to display in the - app + app. </Trans> </Text> @@ -296,7 +296,7 @@ export function LanguageSettingsScreen(_props: Props) { type="button" style={[pal.text, {flexShrink: 1, overflow: 'hidden'}]} numberOfLines={1}> - {myLanguages.length ? myLanguages : 'Select languages'} + {myLanguages.length ? myLanguages : _(msg`Select languages`)} </Text> </Button> </View> diff --git a/src/view/screens/Moderation.tsx b/src/view/screens/Moderation.tsx deleted file mode 100644 index 928766c30..000000000 --- a/src/view/screens/Moderation.tsx +++ /dev/null @@ -1,304 +0,0 @@ -import React from 'react' -import { - ActivityIndicator, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' -import {useFocusEffect} from '@react-navigation/native' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {ComAtprotoLabelDefs} from '@atproto/api' -import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {s} from 'lib/styles' -import {CenteredView} from '../com/util/Views' -import {ViewHeader} from '../com/util/ViewHeader' -import {Link, TextLink} from '../com/util/Link' -import {Text} from '../com/util/text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics/analytics' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {useSetMinimalShellMode} from '#/state/shell' -import {useModalControls} from '#/state/modals' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {ToggleButton} from '../com/util/forms/ToggleButton' -import {useSession} from '#/state/session' -import { - useProfileQuery, - useProfileUpdateMutation, -} from '#/state/queries/profile' -import {ScrollView} from '../com/util/Views' -import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' - -type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'> -export function ModerationScreen({}: Props) { - const pal = usePalette('default') - const {_} = useLingui() - const setMinimalShellMode = useSetMinimalShellMode() - const {screen, track} = useAnalytics() - const {isTabletOrDesktop} = useWebMediaQueries() - const {openModal} = useModalControls() - const {mutedWordsDialogControl} = useGlobalDialogsControlContext() - - useFocusEffect( - React.useCallback(() => { - screen('Moderation') - setMinimalShellMode(false) - }, [screen, setMinimalShellMode]), - ) - - const onPressContentFiltering = React.useCallback(() => { - track('Moderation:ContentfilteringButtonClicked') - openModal({name: 'content-filtering-settings'}) - }, [track, openModal]) - - return ( - <CenteredView - style={[ - s.hContentRegion, - pal.border, - isTabletOrDesktop ? styles.desktopContainer : pal.viewLight, - ]} - testID="moderationScreen"> - <ViewHeader title={_(msg`Moderation`)} showOnDesktop /> - <ScrollView contentContainerStyle={[styles.noBorder]}> - <View style={styles.spacer} /> - <TouchableOpacity - testID="contentFilteringBtn" - style={[styles.linkCard, pal.view]} - onPress={onPressContentFiltering} - accessibilityRole="tab" - accessibilityHint="" - accessibilityLabel={_(msg`Open content filtering settings`)}> - <View style={[styles.iconContainer, pal.btn]}> - <FontAwesomeIcon - icon="eye" - style={pal.text as FontAwesomeIconStyle} - /> - </View> - <Text type="lg" style={pal.text}> - <Trans>Content filtering</Trans> - </Text> - </TouchableOpacity> - <TouchableOpacity - testID="mutedWordsBtn" - style={[styles.linkCard, pal.view]} - onPress={() => mutedWordsDialogControl.open()} - accessibilityRole="tab" - accessibilityHint="" - accessibilityLabel={_(msg`Open muted words settings`)}> - <View style={[styles.iconContainer, pal.btn]}> - <FontAwesomeIcon - icon="filter" - style={pal.text as FontAwesomeIconStyle} - /> - </View> - <Text type="lg" style={pal.text}> - <Trans>Muted words & tags</Trans> - </Text> - </TouchableOpacity> - <Link - testID="moderationlistsBtn" - style={[styles.linkCard, pal.view]} - href="/moderation/modlists"> - <View style={[styles.iconContainer, pal.btn]}> - <FontAwesomeIcon - icon="users-slash" - style={pal.text as FontAwesomeIconStyle} - /> - </View> - <Text type="lg" style={pal.text}> - <Trans>Moderation lists</Trans> - </Text> - </Link> - <Link - testID="mutedAccountsBtn" - style={[styles.linkCard, pal.view]} - href="/moderation/muted-accounts"> - <View style={[styles.iconContainer, pal.btn]}> - <FontAwesomeIcon - icon="user-slash" - style={pal.text as FontAwesomeIconStyle} - /> - </View> - <Text type="lg" style={pal.text}> - <Trans>Muted accounts</Trans> - </Text> - </Link> - <Link - testID="blockedAccountsBtn" - style={[styles.linkCard, pal.view]} - href="/moderation/blocked-accounts"> - <View style={[styles.iconContainer, pal.btn]}> - <FontAwesomeIcon - icon="ban" - style={pal.text as FontAwesomeIconStyle} - /> - </View> - <Text type="lg" style={pal.text}> - <Trans>Blocked accounts</Trans> - </Text> - </Link> - <Text - type="xl-bold" - style={[ - pal.text, - { - paddingHorizontal: 18, - paddingTop: 18, - paddingBottom: 6, - }, - ]}> - <Trans>Logged-out visibility</Trans> - </Text> - <PwiOptOut /> - </ScrollView> - </CenteredView> - ) -} - -function PwiOptOut() { - const pal = usePalette('default') - const {_} = useLingui() - const {currentAccount} = useSession() - const {data: profile} = useProfileQuery({did: currentAccount?.did}) - const updateProfile = useProfileUpdateMutation() - - const isOptedOut = - profile?.labels?.some(l => l.val === '!no-unauthenticated') || false - const canToggle = profile && !updateProfile.isPending - - const onToggleOptOut = React.useCallback(() => { - if (!profile) { - return - } - let wasAdded = false - updateProfile.mutate({ - profile, - updates: existing => { - // create labels attr if needed - existing.labels = ComAtprotoLabelDefs.isSelfLabels(existing.labels) - ? existing.labels - : { - $type: 'com.atproto.label.defs#selfLabels', - values: [], - } - - // toggle the label - const hasLabel = existing.labels.values.some( - l => l.val === '!no-unauthenticated', - ) - if (hasLabel) { - wasAdded = false - existing.labels.values = existing.labels.values.filter( - l => l.val !== '!no-unauthenticated', - ) - } else { - wasAdded = true - existing.labels.values.push({val: '!no-unauthenticated'}) - } - - // delete if no longer needed - if (existing.labels.values.length === 0) { - delete existing.labels - } - return existing - }, - checkCommitted: res => { - const exists = !!res.data.labels?.some( - l => l.val === '!no-unauthenticated', - ) - return exists === wasAdded - }, - }) - }, [updateProfile, profile]) - - return ( - <View style={[pal.view, styles.toggleCard]}> - <View - style={{flexDirection: 'row', alignItems: 'center', paddingRight: 14}}> - <ToggleButton - type="default-light" - label={_( - msg`Discourage apps from showing my account to logged-out users`, - )} - labelType="lg" - isSelected={isOptedOut} - onPress={canToggle ? onToggleOptOut : undefined} - style={[canToggle ? undefined : {opacity: 0.5}, {flex: 1}]} - /> - {updateProfile.isPending && <ActivityIndicator />} - </View> - <View - style={{ - flexDirection: 'column', - gap: 10, - paddingLeft: 66, - paddingRight: 12, - paddingBottom: 10, - marginBottom: 64, - }}> - <Text style={pal.textLight}> - <Trans> - 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. - </Trans> - </Text> - <Text style={[pal.textLight, {fontWeight: '500'}]}> - <Trans> - 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. - </Trans> - </Text> - <TextLink - style={pal.link} - href="https://blueskyweb.zendesk.com/hc/en-us/articles/15835264007693-Data-Privacy" - text={_(msg`Learn more about what is public on Bluesky.`)} - /> - </View> - </View> - ) -} - -const styles = StyleSheet.create({ - desktopContainer: { - borderLeftWidth: 1, - borderRightWidth: 1, - }, - spacer: { - height: 6, - }, - linkCard: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: 12, - paddingHorizontal: 18, - marginBottom: 1, - }, - toggleCard: { - paddingVertical: 8, - paddingTop: 2, - paddingHorizontal: 6, - marginBottom: 1, - }, - iconContainer: { - alignItems: 'center', - justifyContent: 'center', - width: 40, - height: 40, - borderRadius: 30, - marginRight: 12, - }, - noBorder: { - borderBottomWidth: 0, - borderRightWidth: 0, - borderLeftWidth: 0, - borderTopWidth: 0, - }, -}) diff --git a/src/view/screens/ModerationBlockedAccounts.tsx b/src/view/screens/ModerationBlockedAccounts.tsx index 09d77987f..eb3b27048 100644 --- a/src/view/screens/ModerationBlockedAccounts.tsx +++ b/src/view/screens/ModerationBlockedAccounts.tsx @@ -131,7 +131,7 @@ export function ModerationBlockedAccounts({}: Props) { <Text type="lg" style={[pal.text, styles.emptyText]}> <Trans> You have not blocked any accounts yet. To block an account, go - to their profile and selected "Block account" from the menu on + to their profile and select "Block account" from the menu on their account. </Trans> </Text> diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx index 1aff19dd3..911ace778 100644 --- a/src/view/screens/ModerationMutedAccounts.tsx +++ b/src/view/screens/ModerationMutedAccounts.tsx @@ -130,8 +130,8 @@ export function ModerationMutedAccounts({}: Props) { <Text type="lg" style={[pal.text, styles.emptyText]}> <Trans> You have not muted any accounts yet. To mute an account, go to - their profile and selected "Mute account" from the menu on - their account. + their profile and select "Mute account" from the menu on their + account. </Trans> </Text> </View> diff --git a/src/view/screens/NotFound.tsx b/src/view/screens/NotFound.tsx index dfa840abb..7d51619b3 100644 --- a/src/view/screens/NotFound.tsx +++ b/src/view/screens/NotFound.tsx @@ -51,7 +51,13 @@ export const NotFoundScreen = () => { </Text> <Button type="primary" - label={canGoBack ? 'Go back' : 'Go home'} + label={canGoBack ? _(msg`Go Back`) : _(msg`Go Home`)} + accessibilityLabel={canGoBack ? _(msg`Go back`) : _(msg`Go home`)} + accessibilityHint={ + canGoBack + ? _(msg`Returns to previous page`) + : _(msg`Returns to home page`) + } onPress={onPressHome} /> </View> diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx index aa09ab9ed..ba1fa130e 100644 --- a/src/view/screens/PostThread.tsx +++ b/src/view/screens/PostThread.tsx @@ -59,11 +59,7 @@ export function PostThreadScreen({route}: Props) { uri: thread.post.uri, cid: thread.post.cid, text: thread.record.text, - author: { - handle: thread.post.author.handle, - displayName: thread.post.author.displayName, - avatar: thread.post.author.avatar, - }, + author: thread.post.author, embed: thread.post.embed, }, onPost: () => diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index b30b4491b..d5a46c5c9 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -1,5 +1,5 @@ import React, {useMemo} from 'react' -import {StyleSheet, View} from 'react-native' +import {StyleSheet} from 'react-native' import {useFocusEffect} from '@react-navigation/native' import { AppBskyActorDefs, @@ -7,48 +7,39 @@ import { ModerationOpts, RichText as RichTextAPI, } from '@atproto/api' -import {msg, Trans} from '@lingui/macro' +import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {CenteredView} from '../com/util/Views' import {ListRef} from '../com/util/List' -import {ScreenHider} from 'view/com/util/moderation/ScreenHider' -import {Feed} from 'view/com/posts/Feed' +import {ScreenHider} from '#/components/moderation/ScreenHider' import {ProfileLists} from '../com/lists/ProfileLists' import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens' -import {ProfileHeader, ProfileHeaderLoading} from '../com/profile/ProfileHeader' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' import {ErrorScreen} from '../com/util/error/ErrorScreen' -import {EmptyState} from '../com/util/EmptyState' import {FAB} from '../com/util/fab/FAB' import {s, colors} from 'lib/styles' import {useAnalytics} from 'lib/analytics/analytics' import {ComposeIcon2} from 'lib/icons' import {useSetTitle} from 'lib/hooks/useSetTitle' import {combinedDisplayName} from 'lib/strings/display-names' -import { - FeedDescriptor, - resetProfilePostsQueries, -} from '#/state/queries/post-feed' +import {resetProfilePostsQueries} from '#/state/queries/post-feed' import {useResolveDidQuery} from '#/state/queries/resolve-uri' import {useProfileQuery} from '#/state/queries/profile' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useSession, getAgent} from '#/state/session' import {useModerationOpts} from '#/state/queries/preferences' -import {useProfileExtraInfoQuery} from '#/state/queries/profile-extra-info' -import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' +import {useLabelerInfoQuery} from '#/state/queries/labeler' import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell' import {cleanError} from '#/lib/strings/errors' -import {LoadLatestBtn} from '../com/util/load-latest/LoadLatestBtn' -import {useQueryClient} from '@tanstack/react-query' import {useComposerControls} from '#/state/shell/composer' import {listenSoftReset} from '#/state/events' -import {truncateAndInvalidate} from '#/state/queries/util' -import {Text} from '#/view/com/util/text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {isNative} from '#/platform/detection' import {isInvalidHandle} from '#/lib/strings/handles' +import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed' +import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels' +import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header' + interface SectionRef { scrollToTop: () => void } @@ -148,16 +139,24 @@ function ProfileScreenLoaded({ const setMinimalShellMode = useSetMinimalShellMode() const {openComposer} = useComposerControls() const {screen, track} = useAnalytics() + const { + data: labelerInfo, + error: labelerError, + isLoading: isLabelerLoading, + } = useLabelerInfoQuery({ + did: profile.did, + enabled: !!profile.associated?.labeler, + }) const [currentPage, setCurrentPage] = React.useState(0) const {_} = useLingui() const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() - const extraInfoQuery = useProfileExtraInfoQuery(profile.did) const postsSectionRef = React.useRef<SectionRef>(null) const repliesSectionRef = React.useRef<SectionRef>(null) const mediaSectionRef = React.useRef<SectionRef>(null) const likesSectionRef = React.useRef<SectionRef>(null) const feedsSectionRef = React.useRef<SectionRef>(null) const listsSectionRef = React.useRef<SectionRef>(null) + const labelsSectionRef = React.useRef<SectionRef>(null) useSetTitle(combinedDisplayName(profile)) @@ -171,44 +170,75 @@ function ProfileScreenLoaded({ ) const isMe = profile.did === currentAccount?.did + const hasLabeler = !!profile.associated?.labeler + const showFiltersTab = hasLabeler + const showPostsTab = true const showRepliesTab = hasSession + const showMediaTab = !hasLabeler const showLikesTab = isMe - const showFeedsTab = hasSession && (isMe || extraInfoQuery.data?.hasFeedgens) - const showListsTab = hasSession && (isMe || extraInfoQuery.data?.hasLists) + const showFeedsTab = + hasSession && (isMe || (profile.associated?.feedgens || 0) > 0) + const showListsTab = + hasSession && (isMe || (profile.associated?.lists || 0) > 0) + const sectionTitles = useMemo<string[]>(() => { return [ - _(msg`Posts`), + showFiltersTab ? _(msg`Labels`) : undefined, + showListsTab && hasLabeler ? _(msg`Lists`) : undefined, + showPostsTab ? _(msg`Posts`) : undefined, showRepliesTab ? _(msg`Replies`) : undefined, - _(msg`Media`), + showMediaTab ? _(msg`Media`) : undefined, showLikesTab ? _(msg`Likes`) : undefined, showFeedsTab ? _(msg`Feeds`) : undefined, - showListsTab ? _(msg`Lists`) : undefined, + showListsTab && !hasLabeler ? _(msg`Lists`) : undefined, ].filter(Boolean) as string[] - }, [showRepliesTab, showLikesTab, showFeedsTab, showListsTab, _]) + }, [ + showPostsTab, + showRepliesTab, + showMediaTab, + showLikesTab, + showFeedsTab, + showListsTab, + showFiltersTab, + hasLabeler, + _, + ]) let nextIndex = 0 - const postsIndex = nextIndex++ + let filtersIndex: number | null = null + let postsIndex: number | null = null let repliesIndex: number | null = null + let mediaIndex: number | null = null + let likesIndex: number | null = null + let feedsIndex: number | null = null + let listsIndex: number | null = null + if (showFiltersTab) { + filtersIndex = nextIndex++ + } + if (showPostsTab) { + postsIndex = nextIndex++ + } if (showRepliesTab) { repliesIndex = nextIndex++ } - const mediaIndex = nextIndex++ - let likesIndex: number | null = null + if (showMediaTab) { + mediaIndex = nextIndex++ + } if (showLikesTab) { likesIndex = nextIndex++ } - let feedsIndex: number | null = null if (showFeedsTab) { feedsIndex = nextIndex++ } - let listsIndex: number | null = null if (showListsTab) { listsIndex = nextIndex++ } const scrollSectionToTop = React.useCallback( (index: number) => { - if (index === postsIndex) { + if (index === filtersIndex) { + labelsSectionRef.current?.scrollToTop() + } else if (index === postsIndex) { postsSectionRef.current?.scrollToTop() } else if (index === repliesIndex) { repliesSectionRef.current?.scrollToTop() @@ -222,7 +252,15 @@ function ProfileScreenLoaded({ listsSectionRef.current?.scrollToTop() } }, - [postsIndex, repliesIndex, mediaIndex, likesIndex, feedsIndex, listsIndex], + [ + filtersIndex, + postsIndex, + repliesIndex, + mediaIndex, + likesIndex, + feedsIndex, + listsIndex, + ], ) useFocusEffect( @@ -278,6 +316,7 @@ function ProfileScreenLoaded({ return ( <ProfileHeader profile={profile} + labeler={labelerInfo} descriptionRT={hasDescription ? descriptionRT : null} moderationOpts={moderationOpts} hideBackButton={hideBackButton} @@ -286,6 +325,7 @@ function ProfileScreenLoaded({ ) }, [ profile, + labelerInfo, descriptionRT, hasDescription, moderationOpts, @@ -297,8 +337,8 @@ function ProfileScreenLoaded({ <ScreenHider testID="profileView" style={styles.container} - screenDescription="profile" - moderation={moderation.account}> + screenDescription={_(msg`profile`)} + modui={moderation.ui('profileView')}> <PagerWithHeader testID="profilePager" isHeaderReady={!showPlaceholder} @@ -306,19 +346,45 @@ function ProfileScreenLoaded({ onPageSelected={onPageSelected} onCurrentPageSelected={onCurrentPageSelected} renderHeader={renderHeader}> - {({headerHeight, isFocused, scrollElRef}) => ( - <FeedSection - ref={postsSectionRef} - feed={`author|${profile.did}|posts_and_author_threads`} - headerHeight={headerHeight} - isFocused={isFocused} - scrollElRef={scrollElRef as ListRef} - ignoreFilterFor={profile.did} - /> - )} + {showFiltersTab + ? ({headerHeight, scrollElRef}) => ( + <ProfileLabelsSection + ref={labelsSectionRef} + labelerInfo={labelerInfo} + labelerError={labelerError} + isLabelerLoading={isLabelerLoading} + moderationOpts={moderationOpts} + scrollElRef={scrollElRef as ListRef} + headerHeight={headerHeight} + /> + ) + : null} + {showListsTab && !!profile.associated?.labeler + ? ({headerHeight, isFocused, scrollElRef}) => ( + <ProfileLists + ref={listsSectionRef} + did={profile.did} + scrollElRef={scrollElRef as ListRef} + headerOffset={headerHeight} + enabled={isFocused} + /> + ) + : null} + {showPostsTab + ? ({headerHeight, isFocused, scrollElRef}) => ( + <ProfileFeedSection + ref={postsSectionRef} + feed={`author|${profile.did}|posts_and_author_threads`} + headerHeight={headerHeight} + isFocused={isFocused} + scrollElRef={scrollElRef as ListRef} + ignoreFilterFor={profile.did} + /> + ) + : null} {showRepliesTab ? ({headerHeight, isFocused, scrollElRef}) => ( - <FeedSection + <ProfileFeedSection ref={repliesSectionRef} feed={`author|${profile.did}|posts_with_replies`} headerHeight={headerHeight} @@ -328,19 +394,21 @@ function ProfileScreenLoaded({ /> ) : null} - {({headerHeight, isFocused, scrollElRef}) => ( - <FeedSection - ref={mediaSectionRef} - feed={`author|${profile.did}|posts_with_media`} - headerHeight={headerHeight} - isFocused={isFocused} - scrollElRef={scrollElRef as ListRef} - ignoreFilterFor={profile.did} - /> - )} + {showMediaTab + ? ({headerHeight, isFocused, scrollElRef}) => ( + <ProfileFeedSection + ref={mediaSectionRef} + feed={`author|${profile.did}|posts_with_media`} + headerHeight={headerHeight} + isFocused={isFocused} + scrollElRef={scrollElRef as ListRef} + ignoreFilterFor={profile.did} + /> + ) + : null} {showLikesTab ? ({headerHeight, isFocused, scrollElRef}) => ( - <FeedSection + <ProfileFeedSection ref={likesSectionRef} feed={`likes|${profile.did}`} headerHeight={headerHeight} @@ -361,7 +429,7 @@ function ProfileScreenLoaded({ /> ) : null} - {showListsTab + {showListsTab && !profile.associated?.labeler ? ({headerHeight, isFocused, scrollElRef}) => ( <ProfileLists ref={listsSectionRef} @@ -387,77 +455,6 @@ function ProfileScreenLoaded({ ) } -interface FeedSectionProps { - feed: FeedDescriptor - headerHeight: number - isFocused: boolean - scrollElRef: ListRef - ignoreFilterFor?: string -} -const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>( - function FeedSectionImpl( - {feed, headerHeight, isFocused, scrollElRef, ignoreFilterFor}, - ref, - ) { - const {_} = useLingui() - const queryClient = useQueryClient() - const [hasNew, setHasNew] = React.useState(false) - const [isScrolledDown, setIsScrolledDown] = React.useState(false) - - const onScrollToTop = React.useCallback(() => { - scrollElRef.current?.scrollToOffset({ - animated: isNative, - offset: -headerHeight, - }) - truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) - setHasNew(false) - }, [scrollElRef, headerHeight, queryClient, feed, setHasNew]) - React.useImperativeHandle(ref, () => ({ - scrollToTop: onScrollToTop, - })) - - const renderPostsEmpty = React.useCallback(() => { - return <EmptyState icon="feed" message={_(msg`This feed is empty!`)} /> - }, [_]) - - return ( - <View> - <Feed - testID="postsFeed" - enabled={isFocused} - feed={feed} - scrollElRef={scrollElRef} - onHasNew={setHasNew} - onScrolledDownChange={setIsScrolledDown} - renderEmptyState={renderPostsEmpty} - headerOffset={headerHeight} - renderEndOfFeed={ProfileEndOfFeed} - ignoreFilterFor={ignoreFilterFor} - /> - {(isScrolledDown || hasNew) && ( - <LoadLatestBtn - onPress={onScrollToTop} - label={_(msg`Load new posts`)} - showIndicator={hasNew} - /> - )} - </View> - ) - }, -) - -function ProfileEndOfFeed() { - const pal = usePalette('default') - - return ( - <View style={[pal.border, {paddingTop: 32, borderTopWidth: 1}]}> - <Text style={[pal.textLight, pal.border, {textAlign: 'center'}]}> - <Trans>End of feed</Trans> - </Text> - </View> - ) -} - function useRichText(text: string): [RichTextAPI, boolean] { const [prevText, setPrevText] = React.useState(text) const [rawRT, setRawRT] = React.useState(() => new RichTextAPI({text})) diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx index b3a7328c1..8eeeb5d90 100644 --- a/src/view/screens/ProfileFeed.tsx +++ b/src/view/screens/ProfileFeed.tsx @@ -35,7 +35,7 @@ import {ComposeIcon2} from 'lib/icons' import {logger} from '#/logger' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' +import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' import {useFeedSourceInfoQuery, FeedSourceFeedInfo} from '#/state/queries/feed' import {useResolveUriQuery} from '#/state/queries/resolve-uri' import { @@ -108,8 +108,8 @@ export function ProfileFeedScreen(props: Props) { <View style={{flexDirection: 'row'}}> <Button type="default" - accessibilityLabel={_(msg`Go Back`)} - accessibilityHint="Return to previous page" + accessibilityLabel={_(msg`Go back`)} + accessibilityHint={_(msg`Returns to previous page`)} onPress={onPressBack} style={{flexShrink: 1}}> <Text type="button" style={pal.text}> @@ -155,7 +155,7 @@ export function ProfileFeedScreenInner({ const {_} = useLingui() const t = useTheme() const {hasSession, currentAccount} = useSession() - const {openModal} = useModalControls() + const reportDialogControl = useReportDialogControl() const {openComposer} = useComposerControls() const {track} = useAnalytics() const feedSectionRef = React.useRef<SectionRef>(null) @@ -253,13 +253,8 @@ export function ProfileFeedScreenInner({ }, [feedInfo, track]) const onPressReport = React.useCallback(() => { - if (!feedInfo) return - openModal({ - name: 'report', - uri: feedInfo.uri, - cid: feedInfo.cid, - }) - }, [openModal, feedInfo]) + reportDialogControl.open() + }, [reportDialogControl]) const onCurrentPageSelected = React.useCallback( (index: number) => { @@ -400,6 +395,14 @@ export function ProfileFeedScreenInner({ return ( <View style={s.hContentRegion}> + <ReportDialog + control={reportDialogControl} + params={{ + type: 'feedgen', + uri: feedInfo.uri, + cid: feedInfo.cid, + }} + /> <PagerWithHeader items={SECTION_TITLES} isHeaderReady={true} diff --git a/src/view/screens/ProfileFollowers.tsx b/src/view/screens/ProfileFollowers.tsx index 2cad08cb5..6f8ecc2e8 100644 --- a/src/view/screens/ProfileFollowers.tsx +++ b/src/view/screens/ProfileFollowers.tsx @@ -21,7 +21,7 @@ export const ProfileFollowersScreen = ({route}: Props) => { ) return ( - <View> + <View style={{flex: 1}}> <ViewHeader title={_(msg`Followers`)} /> <ProfileFollowersComponent name={name} /> </View> diff --git a/src/view/screens/ProfileFollows.tsx b/src/view/screens/ProfileFollows.tsx index 80502b98b..bdab20153 100644 --- a/src/view/screens/ProfileFollows.tsx +++ b/src/view/screens/ProfileFollows.tsx @@ -21,7 +21,7 @@ export const ProfileFollowsScreen = ({route}: Props) => { ) return ( - <View> + <View style={{flex: 1}}> <ViewHeader title={_(msg`Following`)} /> <ProfileFollowsComponent name={name} /> </View> diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx index 95046e5b4..58b89f239 100644 --- a/src/view/screens/ProfileList.tsx +++ b/src/view/screens/ProfileList.tsx @@ -39,6 +39,7 @@ import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useSetMinimalShellMode} from '#/state/shell' import {useModalControls} from '#/state/modals' +import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' import {useResolveUriQuery} from '#/state/queries/resolve-uri' import { useListQuery, @@ -236,6 +237,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { const {_} = useLingui() const navigation = useNavigation<NavigationProp>() const {currentAccount} = useSession() + const reportDialogControl = useReportDialogControl() const {openModal} = useModalControls() const listMuteMutation = useListMuteMutation() const listBlockMutation = useListBlockMutation() @@ -370,12 +372,8 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { ]) const onPressReport = useCallback(() => { - openModal({ - name: 'report', - uri: list.uri, - cid: list.cid, - }) - }, [openModal, list]) + reportDialogControl.open() + }, [reportDialogControl]) const onPressShare = useCallback(() => { const url = toShareUrl(`/profile/${list.creator.did}/lists/${rkey}`) @@ -550,6 +548,14 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { isOwner={list.creator.did === currentAccount?.did} creator={list.creator} avatarType="list"> + <ReportDialog + control={reportDialogControl} + params={{ + type: 'list', + uri: list.uri, + cid: list.cid, + }} + /> {isCurateList || isPinned ? ( <Button testID={isPinned ? 'unpinBtn' : 'pinBtn'} @@ -907,7 +913,7 @@ function ErrorScreen({error}: {error: string}) { <View style={{flexDirection: 'row'}}> <Button type="default" - accessibilityLabel={_(msg`Go Back`)} + accessibilityLabel={_(msg`Go back`)} accessibilityHint={_(msg`Return to previous page`)} onPress={onPressBack} style={{flexShrink: 1}}> diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index 42eec53d3..d39f37ed7 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -141,6 +141,7 @@ function SearchScreenSuggestedFollows() { friends.slice(0, 4).map(friend => getSuggestedFollowsByActor(friend.did).then(foafsRes => { for (const user of foafsRes.suggestions) { + if (user.associated?.labeler) continue friendsOfFriends.set(user.did, user) } }), @@ -772,7 +773,7 @@ export function SearchScreen( {searchHistory.length > 0 && ( <View style={styles.searchHistoryContent}> <Text style={[pal.text, styles.searchHistoryTitle]}> - Recent Searches + <Trans>Recent Searches</Trans> </Text> {searchHistory.map((historyItem, index) => ( <View key={index} style={styles.historyItemContainer}> diff --git a/src/view/screens/Settings/ExportCarDialog.tsx b/src/view/screens/Settings/ExportCarDialog.tsx index dca51c0dc..ba8fad2df 100644 --- a/src/view/screens/Settings/ExportCarDialog.tsx +++ b/src/view/screens/Settings/ExportCarDialog.tsx @@ -78,8 +78,9 @@ export function ExportCarDialog({ <InlineLink to="https://docs.bsky.app/blog/repo-export" style={[a.text_sm]}> - this blogpost. + this blogpost </InlineLink> + . </Trans> </P> diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx index 3b5e190c1..7e808f910 100644 --- a/src/view/screens/Settings/index.tsx +++ b/src/view/screens/Settings/index.tsx @@ -40,10 +40,7 @@ import { } from '#/state/preferences' import {useSession, useSessionApi, SessionAccount} from '#/state/session' import {useProfileQuery} from '#/state/queries/profile' -import { - useClearPreferencesMutation, - usePreferencesQuery, -} from '#/state/queries/preferences' +import {useClearPreferencesMutation} from '#/state/queries/preferences' // TODO import {useInviteCodesQuery} from '#/state/queries/invites' import {clear as clearStorage} from '#/state/persisted/store' import {clearLegacyStorage} from '#/state/persisted/legacy' @@ -85,7 +82,11 @@ function SettingsAccountCard({account}: {account: SessionAccount}) { const contents = ( <View style={[pal.view, styles.linkCard]}> <View style={styles.avi}> - <UserAvatar size={40} avatar={profile?.avatar} /> + <UserAvatar + size={40} + avatar={profile?.avatar} + type={profile?.associated?.labeler ? 'labeler' : 'user'} + /> </View> <View style={[s.flex1]}> <Text type="md-bold" style={pal.text}> @@ -156,7 +157,6 @@ export function SettingsScreen({}: Props) { const {screen, track} = useAnalytics() const {openModal} = useModalControls() const {isSwitchingAccounts, accounts, currentAccount} = useSession() - const {data: preferences} = usePreferencesQuery() const {mutate: clearPreferences} = useClearPreferencesMutation() // TODO // const {data: invites} = useInviteCodesQuery() @@ -267,6 +267,10 @@ export function SettingsScreen({}: Props) { navigation.navigate('Debug') }, [navigation]) + const onPressDebugModeration = React.useCallback(() => { + navigation.navigate('DebugMod') + }, [navigation]) + const onPressSavedFeeds = React.useCallback(() => { navigation.navigate('SavedFeeds') }, [navigation]) @@ -291,10 +295,7 @@ export function SettingsScreen({}: Props) { return ( <View style={s.hContentRegion} testID="settingsScreen"> <ExportCarDialog control={exportCarControl} /> - <BirthDateSettingsDialog - control={birthdayControl} - preferences={preferences} - /> + <BirthDateSettingsDialog control={birthdayControl} /> <SimpleViewHeader showBackButton={isMobile} @@ -486,20 +487,20 @@ export function SettingsScreen({}: Props) { label={_(msg`System`)} left onSelect={() => setColorMode('system')} - accessibilityHint={_(msg`Set color theme to system setting`)} + accessibilityHint={_(msg`Sets color theme to system setting`)} /> <SelectableBtn selected={colorMode === 'light'} label={_(msg`Light`)} onSelect={() => setColorMode('light')} - accessibilityHint={_(msg`Set color theme to light`)} + accessibilityHint={_(msg`Sets color theme to light`)} /> <SelectableBtn selected={colorMode === 'dark'} label={_(msg`Dark`)} right onSelect={() => setColorMode('dark')} - accessibilityHint={_(msg`Set color theme to dark`)} + accessibilityHint={_(msg`Sets color theme to dark`)} /> </View> </View> @@ -518,14 +519,14 @@ export function SettingsScreen({}: Props) { label={_(msg`Dim`)} left onSelect={() => setDarkTheme('dim')} - accessibilityHint={_(msg`Set dark theme to the dim theme`)} + accessibilityHint={_(msg`Sets dark theme to the dim theme`)} /> <SelectableBtn selected={darkTheme === 'dark'} label={_(msg`Dark`)} right onSelect={() => setDarkTheme('dark')} - accessibilityHint={_(msg`Set dark theme to the dark theme`)} + accessibilityHint={_(msg`Sets dark theme to the dark theme`)} /> </View> </View> @@ -545,8 +546,8 @@ export function SettingsScreen({}: Props) { ]} onPress={openFollowingFeedPreferences} accessibilityRole="button" - accessibilityHint="" - accessibilityLabel={_(msg`Opens the home feed preferences`)}> + accessibilityLabel={_(msg`Following feed preferences`)} + accessibilityHint={_(msg`Opens the Following feed preferences`)}> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon icon="sliders" @@ -566,8 +567,8 @@ export function SettingsScreen({}: Props) { ]} onPress={openThreadsPreferences} accessibilityRole="button" - accessibilityHint="" - accessibilityLabel={_(msg`Opens the threads preferences`)}> + accessibilityLabel={_(msg`Thread preferences`)} + accessibilityHint={_(msg`Opens the threads preferences`)}> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon icon={['far', 'comments']} @@ -586,9 +587,10 @@ export function SettingsScreen({}: Props) { pal.view, isSwitchingAccounts && styles.dimmed, ]} - accessibilityHint="My Saved Feeds" - accessibilityLabel={_(msg`Opens screen with all saved feeds`)} - onPress={onPressSavedFeeds}> + onPress={onPressSavedFeeds} + accessibilityRole="button" + accessibilityLabel={_(msg`My saved feeds`)} + accessibilityHint={_(msg`Opens screen with all saved feeds`)}> <View style={[styles.iconContainer, pal.btn]}> <HashtagIcon style={pal.text} size={18} strokeWidth={3} /> </View> @@ -687,7 +689,7 @@ export function SettingsScreen({}: Props) { onPress={onPressAppPasswords} accessibilityRole="button" accessibilityLabel={_(msg`App password settings`)} - accessibilityHint={_(msg`Opens the app password settings page`)}> + accessibilityHint={_(msg`Opens the app password settings`)}> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon icon="lock" @@ -708,7 +710,9 @@ export function SettingsScreen({}: Props) { onPress={isSwitchingAccounts ? undefined : onPressChangeHandle} accessibilityRole="button" accessibilityLabel={_(msg`Change handle`)} - accessibilityHint={_(msg`Choose a new Bluesky username or create`)}> + accessibilityHint={_( + msg`Opens modal for choosing a new Bluesky handle`, + )}> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon icon="at" @@ -744,7 +748,9 @@ export function SettingsScreen({}: Props) { onPress={() => openModal({name: 'change-password'})} accessibilityRole="button" accessibilityLabel={_(msg`Change password`)} - accessibilityHint={_(msg`Change your Bluesky password`)}> + accessibilityHint={_( + msg`Opens modal for changing your Bluesky password`, + )}> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon icon="lock" @@ -766,7 +772,7 @@ export function SettingsScreen({}: Props) { accessibilityRole="button" accessibilityLabel={_(msg`Export my data`)} accessibilityHint={_( - msg`Download Bluesky account data (repository)`, + msg`Opens modal for downloading your Bluesky account data (repository)`, )}> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon @@ -785,7 +791,7 @@ export function SettingsScreen({}: Props) { accessibilityRole="button" accessibilityLabel={_(msg`Delete account`)} accessibilityHint={_( - msg`Opens modal for account deletion confirmation. Requires email code.`, + msg`Opens modal for account deletion confirmation. Requires email code`, )}> <View style={[styles.iconContainer, dangerBg]}> <FontAwesomeIcon @@ -803,8 +809,8 @@ export function SettingsScreen({}: Props) { style={[pal.view, styles.linkCardNoIcon]} onPress={onPressSystemLog} accessibilityRole="button" - accessibilityHint="Open system log" - accessibilityLabel={_(msg`Opens the system log page`)}> + accessibilityLabel={_(msg`Open system log`)} + accessibilityHint={_(msg`Opens the system log page`)}> <Text type="lg" style={pal.text}> <Trans>System log</Trans> </Text> @@ -823,9 +829,19 @@ export function SettingsScreen({}: Props) { </TouchableOpacity> <TouchableOpacity style={[pal.view, styles.linkCardNoIcon]} + onPress={onPressDebugModeration} + accessibilityRole="button" + accessibilityLabel={_(msg`Open storybook page`)} + accessibilityHint={_(msg`Opens the storybook page`)}> + <Text type="lg" style={pal.text}> + <Trans>Debug Moderation</Trans> + </Text> + </TouchableOpacity> + <TouchableOpacity + style={[pal.view, styles.linkCardNoIcon]} onPress={onPressResetPreferences} accessibilityRole="button" - accessibilityLabel={_(msg`Reset preferences`)} + accessibilityLabel={_(msg`Reset preferences state`)} accessibilityHint={_(msg`Resets the preferences state`)}> <Text type="lg" style={pal.text}> <Trans>Reset preferences state</Trans> @@ -835,7 +851,7 @@ export function SettingsScreen({}: Props) { style={[pal.view, styles.linkCardNoIcon]} onPress={onPressResetOnboarding} accessibilityRole="button" - accessibilityLabel={_(msg`Reset onboarding`)} + accessibilityLabel={_(msg`Reset onboarding state`)} accessibilityHint={_(msg`Resets the onboarding state`)}> <Text type="lg" style={pal.text}> <Trans>Reset onboarding state</Trans> @@ -846,7 +862,7 @@ export function SettingsScreen({}: Props) { onPress={clearAllLegacyStorage} accessibilityRole="button" accessibilityLabel={_(msg`Clear all legacy storage data`)} - accessibilityHint={_(msg`Clear all legacy storage data`)}> + accessibilityHint={_(msg`Clears all legacy storage data`)}> <Text type="lg" style={pal.text}> <Trans> Clear all legacy storage data (restart after this) @@ -858,7 +874,7 @@ export function SettingsScreen({}: Props) { onPress={clearAllStorage} accessibilityRole="button" accessibilityLabel={_(msg`Clear all storage data`)} - accessibilityHint={_(msg`Clear all storage data`)}> + accessibilityHint={_(msg`Clears all storage data`)}> <Text type="lg" style={pal.text}> <Trans>Clear all storage data (restart after this)</Trans> </Text> @@ -947,7 +963,7 @@ function EmailConfirmationNotice() { ]} accessibilityRole="button" accessibilityLabel={_(msg`Verify my email`)} - accessibilityHint="" + accessibilityHint={_(msg`Opens modal for email verification`)} onPress={() => openModal({name: 'verify-email'})}> <FontAwesomeIcon icon="envelope" diff --git a/src/view/screens/Storybook/Buttons.tsx b/src/view/screens/Storybook/Buttons.tsx index 320db13ff..ad2fff3f4 100644 --- a/src/view/screens/Storybook/Buttons.tsx +++ b/src/view/screens/Storybook/Buttons.tsx @@ -129,6 +129,15 @@ export function Buttons() { <ButtonIcon icon={Globe} position="left" /> <ButtonText>Link out</ButtonText> </Button> + + <Button + variant="gradient" + color="gradient_sky" + size="tiny" + label="Link out"> + <ButtonIcon icon={Globe} position="left" /> + <ButtonText>Link out</ButtonText> + </Button> </View> <View style={[a.flex_row, a.gap_md, a.align_start]}> @@ -149,6 +158,14 @@ export function Buttons() { <ButtonIcon icon={ChevronLeft} /> </Button> <Button + variant="gradient" + color="gradient_sunset" + size="tiny" + shape="round" + label="Link out"> + <ButtonIcon icon={ChevronLeft} /> + </Button> + <Button variant="outline" color="primary" size="large" @@ -164,6 +181,14 @@ export function Buttons() { label="Link out"> <ButtonIcon icon={ChevronLeft} /> </Button> + <Button + variant="ghost" + color="primary" + size="tiny" + shape="round" + label="Link out"> + <ButtonIcon icon={ChevronLeft} /> + </Button> </View> <View style={[a.flex_row, a.gap_md, a.align_start]}> @@ -184,6 +209,14 @@ export function Buttons() { <ButtonIcon icon={ChevronLeft} /> </Button> <Button + variant="gradient" + color="gradient_sunset" + size="tiny" + shape="square" + label="Link out"> + <ButtonIcon icon={ChevronLeft} /> + </Button> + <Button variant="outline" color="primary" size="large" @@ -199,6 +232,14 @@ export function Buttons() { label="Link out"> <ButtonIcon icon={ChevronLeft} /> </Button> + <Button + variant="ghost" + color="primary" + size="tiny" + shape="square" + label="Link out"> + <ButtonIcon icon={ChevronLeft} /> + </Button> </View> </View> ) diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx index e43d756de..3a2e2f369 100644 --- a/src/view/screens/Storybook/index.tsx +++ b/src/view/screens/Storybook/index.tsx @@ -67,6 +67,7 @@ export function Storybook() { </Button> </View> + <Dialogs /> <ThemeProvider theme="light"> <Theming /> </ThemeProvider> diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index 2a37d1fe9..1bf5647f6 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -75,6 +75,7 @@ let DrawerProfileCard = ({ avatar={profile?.avatar} // See https://github.com/bluesky-social/social-app/pull/1801: usePlainRNImage={true} + type={profile?.associated?.labeler ? 'labeler' : 'user'} /> <Text type="title-lg" @@ -93,10 +94,12 @@ let DrawerProfileCard = ({ {formatCountShortOnly(profile?.followersCount ?? 0)} </Text>{' '} {pluralize(profile?.followersCount || 0, 'follower')} ·{' '} - <Text type="xl-medium" style={pal.text}> - {formatCountShortOnly(profile?.followsCount ?? 0)} - </Text>{' '} - following + <Trans> + <Text type="xl-medium" style={pal.text}> + {formatCountShortOnly(profile?.followsCount ?? 0)} + </Text>{' '} + following + </Trans> </Text> </TouchableOpacity> ) diff --git a/src/view/shell/NavSignupCard.tsx b/src/view/shell/NavSignupCard.tsx index bae37e838..83d141498 100644 --- a/src/view/shell/NavSignupCard.tsx +++ b/src/view/shell/NavSignupCard.tsx @@ -58,7 +58,7 @@ let NavSignupCard = ({}: {}): React.ReactNode => { accessibilityHint={_(msg`Sign in`)} accessibilityLabel={_(msg`Sign in`)}> <Text type="md" style={[pal.text, s.bold]}> - Sign in + <Trans>Sign in</Trans> </Text> </Button> </View> diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx index 115faa296..8a19a0b4f 100644 --- a/src/view/shell/bottom-bar/BottomBar.tsx +++ b/src/view/shell/bottom-bar/BottomBar.tsx @@ -229,6 +229,7 @@ export function BottomBar({navigation}: BottomTabBarProps) { size={27} // See https://github.com/bluesky-social/social-app/pull/1801: usePlainRNImage={true} + type={profile?.associated?.labeler ? 'labeler' : 'user'} /> </View> ) : ( @@ -238,6 +239,7 @@ export function BottomBar({navigation}: BottomTabBarProps) { size={28} // See https://github.com/bluesky-social/social-app/pull/1801: usePlainRNImage={true} + type={profile?.associated?.labeler ? 'labeler' : 'user'} /> </View> )} diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index c56ba941e..097ca2fbf 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -64,7 +64,11 @@ function ProfileCard() { style={[styles.profileCard, !isDesktop && styles.profileCardTablet]} title={_(msg`My Profile`)} asAnchor> - <UserAvatar avatar={profile.avatar} size={size} /> + <UserAvatar + avatar={profile.avatar} + size={size} + type={profile?.associated?.labeler ? 'labeler' : 'user'} + /> </Link> ) : ( <View style={[styles.profileCard, !isDesktop && styles.profileCardTablet]}> diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx index 4a9483733..0c5bd452f 100644 --- a/src/view/shell/desktop/Search.tsx +++ b/src/view/shell/desktop/Search.tsx @@ -11,7 +11,7 @@ import {useNavigation, StackActions} from '@react-navigation/native' import { AppBskyActorDefs, moderateProfile, - ProfileModeration, + ModerationDecision, } from '@atproto/api' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -86,7 +86,7 @@ export function SearchProfileCard({ moderation, }: { profile: AppBskyActorDefs.ProfileViewBasic - moderation: ProfileModeration + moderation: ModerationDecision }) { const pal = usePalette('default') @@ -111,7 +111,8 @@ export function SearchProfileCard({ <UserAvatar size={40} avatar={profile.avatar} - moderation={moderation.avatar} + moderation={moderation.ui('avatar')} + type={profile.associated?.labeler ? 'labeler' : 'user'} /> <View style={{flex: 1}}> <Text @@ -121,7 +122,7 @@ export function SearchProfileCard({ lineHeight={1.2}> {sanitizeDisplayName( profile.displayName || sanitizeHandle(profile.handle), - moderation.profile, + moderation.ui('displayName'), )} </Text> <Text type="md" style={[pal.textLight]} numberOfLines={1}> diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 76a7f8fb3..f29183095 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -101,8 +101,8 @@ function ShellInner() { <Composer winHeight={winDim.height} /> <ModalsContainer /> <MutedWordsDialog /> - <PortalOutlet /> <Lightbox /> + <PortalOutlet /> </> ) } diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 71dccb8c4..02993ac46 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -1,5 +1,9 @@ import React, {useEffect} from 'react' import {View, StyleSheet, TouchableOpacity} from 'react-native' +import {useNavigation} from '@react-navigation/native' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + import {ErrorBoundary} from '../com/util/ErrorBoundary' import {Lightbox} from '../com/lightbox/Lightbox' import {ModalsContainer} from '../com/modals/Modal' @@ -9,9 +13,7 @@ import {s, colors} from 'lib/styles' import {RoutesContainer, FlatNavigator} from '../../Navigation' import {DrawerContent} from './Drawer' import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries' -import {useNavigation} from '@react-navigation/native' import {NavigationProp} from 'lib/routes/types' -import {t} from '@lingui/macro' import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell' import {useCloseAllActiveElements} from '#/state/util' import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' @@ -24,6 +26,7 @@ function ShellInner() { const {isDesktop} = useWebMediaQueries() const navigator = useNavigation<NavigationProp>() const closeAllActiveElements = useCloseAllActiveElements() + const {_} = useLingui() useWebBodyScrollLock(isDrawerOpen) @@ -42,14 +45,15 @@ function ShellInner() { <Composer winHeight={0} /> <ModalsContainer /> <MutedWordsDialog /> - <PortalOutlet /> <Lightbox /> + <PortalOutlet /> + {!isDesktop && isDrawerOpen && ( <TouchableOpacity onPress={() => setDrawerOpen(false)} style={styles.drawerMask} - accessibilityLabel={t`Close navigation footer`} - accessibilityHint={t`Closes bottom navigation bar`}> + accessibilityLabel={_(msg`Close navigation footer`)} + accessibilityHint={_(msg`Closes bottom navigation bar`)}> <View style={styles.drawerContainer}> <DrawerContent /> </View> diff --git a/yarn.lock b/yarn.lock index 28d821028..e9783d786 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,19 +34,17 @@ jsonpointer "^5.0.0" leven "^3.1.0" -"@atproto/api@^0.10.5": - version "0.10.5" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.10.5.tgz#e778e2843d08690df8df81f24028a7578e9b3cb4" - integrity sha512-GYdST5sPKU2JnPmm8x3KqjOSlDiYXrp4GkW7bpQTVLPabnUNq5NLN6HJEoJABjjOAsaLF12rBoV+JpRb1UjNsQ== - dependencies: - "@atproto/common-web" "^0.2.3" - "@atproto/lexicon" "^0.3.2" - "@atproto/syntax" "^0.2.0" - "@atproto/xrpc" "^0.4.2" +"@atproto/api@^0.12.1": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.1.tgz#3340cbbd6a51a8c2f3248dae55a01415ab71084e" + integrity sha512-Grigs9neuQxytXr2yHq/IfNlgXQVptWDO9KTQr5FDmgMY4Zly2X7Sa99u9c1CW9auwUTbcd+yRFBNEtbA3n3qg== + 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" - typed-emitter "^2.1.0" - zod "^3.21.4" "@atproto/api@^0.9.5": version "0.9.5" @@ -144,6 +142,16 @@ uint8arrays "3.0.0" zod "^3.21.4" +"@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.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210" @@ -245,13 +253,13 @@ multiformats "^9.9.0" zod "^3.21.4" -"@atproto/lexicon@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.3.2.tgz#0085a3acd3a77867b8efe188297a1bbacc55ce5c" - integrity sha512-kmGCkrRwpWIqmn/KO4BZwUf8Nmfndk3XvFC06V0ygCWc42g6+t4QP/6ywNW4PgqfZY0Q5aW4EuDfD7KjAFkFtQ== +"@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.2.3" - "@atproto/syntax" "^0.2.0" + "@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" @@ -351,12 +359,10 @@ dependencies: "@atproto/common-web" "^0.2.3" -"@atproto/syntax@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.2.0.tgz#4bab724c02e11f8943b8ec101251082cf55067e9" - integrity sha512-K+9jl6mtxC9ytlR7msSiP9jVNqtdxEBSt0kOfsC924lqGwuD8nlUAMi1GSMgAZJGg/Rd+0MKXh789heTdeL3HQ== - dependencies: - "@atproto/common-web" "^0.2.3" +"@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-server@^0.4.2": version "0.4.2" @@ -383,12 +389,12 @@ "@atproto/lexicon" "^0.3.1" zod "^3.21.4" -"@atproto/xrpc@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.4.2.tgz#57812e0624be597b85f21471acf336513f35ccda" - integrity sha512-x4x2QB4nWmLjIpz2Ue9n/QVbVyJkk6tQMhvmDQaVFF89E3FcVI4rxF4uhzSxaLpbNtyVQBNEEmNHOr5EJLeHVA== +"@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.3.2" + "@atproto/lexicon" "^0.4.0" zod "^3.21.4" "@aws-crypto/crc32@3.0.0": @@ -7244,11 +7250,37 @@ dependencies: "@tamagui/constants" "1.84.1" +"@tanstack/query-async-storage-persister@^5.25.0": + version "5.25.0" + resolved "https://registry.yarnpkg.com/@tanstack/query-async-storage-persister/-/query-async-storage-persister-5.25.0.tgz#0e8a2a781b8e32a81a5d02a688d6fcdfd055235b" + integrity sha512-58UTp1CuLr2mehsJRMOd8IZPtYGHFeL+uHnHyRd1kmbwo7wDaa8HXstiBdTRq5KokxIXy9FiFbA06LtKuOiwMQ== + dependencies: + "@tanstack/query-persist-client-core" "5.25.0" + +"@tanstack/query-core@5.25.0": + version "5.25.0" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.25.0.tgz#e08ed0a9fad34c8005d1a282e57280031ac50cdc" + integrity sha512-vlobHP64HTuSE68lWF1mEhwSRC5Q7gaT+a/m9S+ItuN+ruSOxe1rFnR9j0ACWQ314BPhBEVKfBQ6mHL0OWfdbQ== + "@tanstack/query-core@5.8.1": version "5.8.1" resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.8.1.tgz#5215a028370d9b2f32e83787a0ea119e2f977996" integrity sha512-Y0enatz2zQXBAsd7XmajlCs+WaitdR7dIFkqz9Xd7HL4KV04JOigWVreYseTmNH7YFSBSC/BJ9uuNp1MAf+GfA== +"@tanstack/query-persist-client-core@5.25.0": + version "5.25.0" + resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-5.25.0.tgz#52fa634a8067d7b965854a532a33077fd4df0eff" + integrity sha512-sEUsEZ/XWkOosO45CDBI5nj5woCS+DUd9Dk8pGpU8MkeH0EVd3p4N5CdbjNhrreyy5Krf3rpNaiRN9ygLX/rWA== + dependencies: + "@tanstack/query-core" "5.25.0" + +"@tanstack/react-query-persist-client@^5.25.0": + version "5.25.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.25.0.tgz#ecbd1362cd6fd94e723d54f5af477d0812852dab" + integrity sha512-j1+GyFj4UQGWuiFZoDUVJZS+wxqKd9SGvPlyHG619zzYNN+QQu4B5uvvHc6U8MroM377EOBOuLKK3W6qsAdahQ== + dependencies: + "@tanstack/query-persist-client-core" "5.25.0" + "@tanstack/react-query@^5.8.1": version "5.8.1" resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.8.1.tgz#22a122016e23a39acd90341954a895980ec21ade" |